Bug 8410 - ImageView Memory Leak following orientation change
Summary: ImageView Memory Leak following orientation change
Status: RESOLVED INVALID
Alias: None
Product: Android
Classification: Xamarin
Component: General ()
Version: 4.8.x
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2012-11-14 18:38 UTC by Wayne Phipps
Modified: 2012-11-16 22:35 UTC (History)
3 users (show)

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


Attachments
Stack Trace (2.64 KB, text/plain)
2012-11-14 18:38 UTC, Wayne Phipps
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 INVALID

Description Wayne Phipps 2012-11-14 18:38:57 UTC
Created attachment 2930 [details]
Stack Trace

Description of Problem:
I experienced a 'Java.Lang.OutOfMemoryError' exception assigning an image in the OnCreate method following orientation change on the Nexus 7.

Steps to reproduce the problem:
I stripped down the code to the simplest project and managed to reproduce the issue with a single activity and only the following method:

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.Main);

        //get a reference to the ImageView
        var imageView = FindViewById<ImageView>(Resource.Id.imageView1);
        imageView.SetImageBitmap( Android.Graphics.BitmapFactory.DecodeResource( this.Resources, Resource.Drawable.Ready) );
    }

The Main.mxl layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:minWidth="25px"
    android:minHeight="25px">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView1"
        android:layout_gravity="center_vertical" />
</LinearLayout>


Actual Results:
Java.Lang.OutOfMemoryError

Expected Results:
No error

How often does this happen? 
I counted 27 shifts in orientation before the exception was thrown. 

Additional Information:
MonoDevelop v3.0.5
Comment 1 Jonathan Pryor 2012-11-16 22:35:46 UTC
This is a known limitation that cannot be solved at this time:

http://docs.xamarin.com/Android/Guides/Advanced_Topics/Garbage_Collection#Helping_the_GC

The problem has two parts:

1. Each Java.Lang.Object subclass that is created holds a JNI Global Reference (gref) to keep the corresponding Java object alive.

2. The Java-side gref won't be collected until the Mono-side instance is collected.

Reproduction:

Take your sample code, but instead of `Resource.Drawable.Ready` use `Resource.Drawable.Icon` (allows it to work with the default template).

Next:

http://docs.xamarin.com/Android/Guides/Deployment%2C_Testing%2C_and_Metrics/Diagnostics#Global_Reference_Messages

    $ adb shell setprop debug.mono.log gref

Restart the app, and in logcat you'll see:

> I/monodroid-gref(15973): +g+ grefc 1 gwrefc 0 obj-handle 0x416a6f40/L -> new-handle 0x1d20021e/G from    at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)
...
> I/monodroid-gref(15973): +g+ grefc 58 gwrefc 0 obj-handle 0x41762d20/L -> new-handle 0x1d20034e/G from    at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value,

The key part is "grefc VALUE", which is the number of outstanding grefs. Rotate the app a few times, and you'll see that the gref count constantly increases and doesn't decrease.

Why doesn't it decrease? Because no objects are being collected. Why are no objects being collected? Because there are no Garbage Collections.

Solution 1:

To demonstrate this point, modify OnCreate() to call System.GC.Collect() and re-run the test. You'll see that the gref count stabilizes at 12, as opposed to the 58 observed above.

This is why we say that Mono's GC has "an incomplete view of the process." Mono's GC has seen lots of small objects allocated, but not enough to warrant a collection. Mono's GC doesn't know that some of those instances are referencing Java-side objects which are (potentially) huge, as is often the case with bitmaps.

Calling GC.Collect() let's Mono's GC know that it should run, and things stabilize. However, this is a global solution to a local problem.

Solution 2:

We can do better, by disposing of the wrapper objects when we "know" we don't need them anymore. (Note: care must be taken about "knowing" that a wrapper won't be needed anymore.)

Change OnCreate() to:

	protected override void OnCreate (Bundle bundle)
	{
		base.OnCreate (bundle);

		// Set our view from the "main" layout resource
		SetContentView (Resource.Layout.Main);

		//get a reference to the ImageView
		using (var imageView = FindViewById<ImageView>(Resource.Id.imageView1))
		using (var bitmap    = Android.Graphics.BitmapFactory.DecodeResource(
				this.Resources, Resource.Drawable.Icon))
			imageView.SetImageBitmap(bitmap);
	}

Unlike Solution 1, this will _not_ stabilize the gref count...because every time  you rotate, a new Activity is created [0], but it _will_ release the Bitmap instance so that only Java is referencing it. This allows the Java GC to free the BItmap instance when Java collects the dead Activity instances, because there won't be an outstanding gref keeping the Bitmap alive longer than necessary.

[0] ...and the Activity gref can be released by overriding Activity.OnDestroy() and calling Dispose(), but there are diminishing returns in trying to Dispose() everything. At some point, either the GC will collect, or you'll need to prompt it.