Bug 8190 - Garbage collector leaks
Summary: Garbage collector leaks
Status: RESOLVED FEATURE
Alias: None
Product: iOS
Classification: Xamarin
Component: XI runtime ()
Version: 6.0.x
Hardware: PC Windows
: --- normal
Target Milestone: Untriaged
Assignee: Rolf Bjarne Kvinge [MSFT]
URL:
Depends on:
Blocks:
 
Reported: 2012-11-02 19:31 UTC by randyficker
Modified: 2012-11-15 09:35 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Repro (2.95 KB, text/plain)
2012-11-02 19:38 UTC, randyficker
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED FEATURE

Description randyficker 2012-11-02 19:31:14 UTC
Since I started profiling my MonoTouch app, I've discovered that it's extremely leaky.  Practically everything I allocated wasn't getting collected.

After considerable time and effort I was able to whittle down my code to a small sample that reproduces the issue. Running the sample below in the latest stable MonoTouch (6.0.4 at the time of this writing) causes memory to endlessly leak until the app crashes.  

This repros in all situations I've tested it in:  Both in the simulator and on the device, and both with and without SGen check-box checked, and both with and without the referencing counting extension check-box checked.

using System;
using MonoTouch.UIKit;
using MonoTouch.Foundation;

[Register("AppDelegate")]
class WamlEntryPoint : UIApplicationDelegate
{
	static void Main(string[] args)
	{
		UIApplication.Main(args, null, "AppDelegate");
	}

	private UIWindow MainWindow;
	private Screen _rootInstance;
	private UINavigationController _navigationController;
	private UILabel _lbl;

	public override void FinishedLaunching(UIApplication app)
	{
		//Create the UIWindow and UINavigationController
		MainWindow = new UIWindow(UIScreen.MainScreen.Bounds);
		MainWindow.RootViewController = _navigationController = new UINavigationController();

		//Create an instance of Screen and push it
		_rootInstance = new Screen();
		_navigationController.PushViewController(_rootInstance.ViewController, false);

		//Create a label for reporting the memory usage
		_lbl = new UILabel();
		_lbl.Frame = UIScreen.MainScreen.Bounds;
		_rootInstance.ScreenView.AddSubview(_lbl);

		//Kick off an automated loop after a second
		NSTimer.CreateScheduledTimer(1, Cycle);

		MainWindow.MakeKeyAndVisible();
	}


	/// <summary>
	/// Creates a new screen, pushes it on the nav controller and then immediately pops it off.  Then just report total memory used.
	/// </summary>
	void Cycle()
	{
		var newScreen = new Screen();

		//This is the magic that demonstrates the GC bug - some circular relationship of objects.
		var foo = new Foo();
		foo.Parent = newScreen;
		newScreen.ScreenView.AddSubview(foo.FoosView);

		//Push/pop it
		_navigationController.PushViewController(newScreen.ViewController, false);
		_navigationController.PopViewControllerAnimated(false);

		//Report memory used
		this._lbl.Text = "Total Memory Used = " + GC.GetTotalMemory(true);

		//Do it again in half a second
		NSTimer.CreateScheduledTimer(.5, Cycle);
	}
}

public class Screen
{
	public UIView ScreenView;
	public MyUIViewControler ViewController;

	public Screen()
	{
		ViewController = new MyUIViewControler(this);
		ScreenView = new UIView();
	}
}


public class MyUIViewControler : UIViewController
{
	byte[] buffer = new byte[100000]; //artificially use more memory so that we can easily see if this is being collected when we call GC.GetTotalMemory()

	public Screen Screen;

	public MyUIViewControler(Screen s)
	{
		Screen = s;
	}

	public override void LoadView()
	{
		this.View = Screen.ScreenView;
	}
}


public class Foo
{
	public Screen Parent;
	public MyUIView FoosView;

	public Foo()
	{
		FoosView = new MyUIView(this);
	}
}

public class MyUIView : UIView
{
	private Foo _foo;

	public MyUIView(Foo parent)
	{
		_foo = parent;
	}
}
Comment 1 randyficker 2012-11-02 19:38:19 UTC
Created attachment 2847 [details]
Repro

Attaching the same code that's pasted in the bug since bugzilla seems to insert newlines on long lines.
Comment 2 Rolf Bjarne Kvinge [MSFT] 2012-11-15 09:35:58 UTC
This is an unfortunate side-effect of having to co-exist with ObjectiveC's reference counting.

I have explained this somewhat here: http://stackoverflow.com/a/13059140/183422 - the sample there isn't identical to yours but it is the same root cause. In short: ObjectiveC keeps the tree of objects in top->bottom order from being freed, and the managed GC keeps the tree of objects in bottom->top order from being freed.

One way to fix this in your case is to use weak gchandles in the bottom->top line (so Foo would create a weak gchandle for its Parent property, and MyUIView would create a weak gchandle for its _foo property).

We have plans to improve this scenario (probably by better tools to detect this and help you understand what's going on), but it's a complex subject so it will take some time.