Bug 13094 - Bitmap memory issues
Summary: Bitmap memory issues
Status: RESOLVED INVALID
Alias: None
Product: Android
Classification: Xamarin
Component: Mono runtime / AOT Compiler ()
Version: 4.6.x
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2013-07-08 09:58 UTC by Goncalo Oliveira
Modified: 2013-07-11 06:56 UTC (History)
2 users (show)

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

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 Goncalo Oliveira 2013-07-08 09:58:54 UTC
Hi,

I'm facing an issue with Google maps and custom bitmaps as marker icons and I'm a little confused if I'm doing the thing the wrong way or there's something wrong happening either with Google maps component or with the Xamarin - JVM bridge.

1. I'm placing a huge quantity of markers on the map. Let's start with 900. I'm generating random locations around a point to create these much different LatLng objects.

Next, I do the following for each LatLng item.

                string title = "Item " + ( itemIndex++ ).ToString(); // just for readability

                var iconMarkerOption = new MarkerOptions()
                .SetPosition( item )
                .SetSnippet( "Snippet" )
                .Anchor( 0.5f, 0.5f )
                .SetTitle( title );

                map.AddMarker( iconMarkerOption );

Build, Run and... all's well. 900 markers on the screen and not a care in the world.

2. I don't want to use the default image, to I change the code to use my own.

                var iconMarkerOption = new MarkerOptions()
                .SetPosition( item )
                .SetSnippet( "Snippet" )
                .Anchor( 0.5f, 0.5f )
                .InvokeIcon( BitmapDescriptorFactory.FromResource( Resource.Drawable.ic_marker1 ) )
                .SetTitle( title );

Again, build, run and... all's well. 900 markers on the screen with my own icon.

3. I don't want just a standard image, I want to add some label text to each icon. So I create the following method.

        private Paint paint = new Paint( PaintFlags.AntiAlias );
        private Rect bounds = new Rect();
        private BitmapDescriptor GetCustomBitmapDescriptor( string text )
        {
            Bitmap baseBitmap = BitmapFactory.DecodeResource( Resources, Resource.Drawable.ic_marker1 );
            Bitmap bitmap = baseBitmap.Copy( Bitmap.Config.Argb8888, true );

            paint.GetTextBounds( text, 0, text.Length, bounds );

            float x = bitmap.Width / 2.0f;
            float y = ( bitmap.Height - bounds.Height() ) / 2.0f - bounds.Top;

            Canvas canvas = new Canvas( bitmap );

            canvas.DrawText( text, x, y, paint );

            BitmapDescriptor icon = BitmapDescriptorFactory.FromBitmap( bitmap );

            return ( icon );
        }

and I change my code

                var iconMarkerOption = new MarkerOptions()
                .SetPosition( item )
                .SetSnippet( "Snippet" )
                .Anchor( 0.5f, 0.5f )
                .InvokeIcon( GetCustomBitmapDescriptor( title ) )
                .SetTitle( title );

Build, run and... out of memory.
07-04 15:32:35.055: E/mono(6005): Unhandled Exception:
07-04 15:32:35.055: E/mono(6005): Java.Lang.OutOfMemoryError: Exception of type 'Java.Lang.OutOfMemoryError' was thrown.
07-04 15:32:35.055: E/mono(6005): at Android.Runtime.JNIEnv.CallStaticObjectMethod (intptr,intptr,Android.Runtime.JValue[]) <0x00080>
07-04 15:32:35.055: E/mono(6005): at Android.Gms.Maps.Model.BitmapDescriptorFactory.FromBitmap (Android.Graphics.Bitmap) <0x00103>
07-04 15:32:35.055: E/mono(6005): at MapsExtensionsTest.MainActivity.GetCustomBitmapDescriptor (string) <0x001b7>
07-04 15:32:35.055: E/mono(6005): at MapsExtensionsTest.MainActivity.button_Click (object,System.EventArgs) <0x00187>
07-04 15:32:35.055: E/mono(6005): at Android.Views.View/IOnClickListenerImplementor.OnClick (Android.Views.View) <0x0005b>
07-04 15:32:35.055: E/mono(6005): at Android.Views.View/IOnClickListenerInvoker.n_OnClick_Landroid_view_View_ (intptr,intptr,intptr) <0x0005b>
07-04 15:32:35.055: E/mono(6005): at (wrapper dynamic-method) object.d67d7456-e896-445d-be35-db0b39982fc0 (intptr,intptr,intptr) <0x00043>
07-04 15:32:35.055: E/mono(6005):   --- End of managed exception stack trace ---
07-04 15:32:35.055: E/mono(6005): java.lang.OutOfMemoryError
07-04 15:32:35.055: E/mono(6005): 	at android.graphics.Bitmap.nativeCreateFromParcel(Native Method)

I tried changing the baseBitmap decoding to some place else, so that it's only called once, as it's kind of lame to do this every single time.

        private Paint paint = new Paint( PaintFlags.AntiAlias );
        private Rect bounds = new Rect();
        Bitmap baseBitmap = null;
        private BitmapDescriptor GetCustomBitmapDescriptor( string text )
        {
            if ( baseBitmap == null )
            {
                baseBitmap = BitmapFactory.DecodeResource( Resources, Resource.Drawable.ic_marker1 );
            }

            Bitmap bitmap = baseBitmap.Copy( Bitmap.Config.Argb8888, true );

            paint.GetTextBounds( text, 0, text.Length, bounds );

            float x = bitmap.Width / 2.0f;
            float y = ( bitmap.Height - bounds.Height() ) / 2.0f - bounds.Top;

            Canvas canvas = new Canvas( bitmap );

            canvas.DrawText( text, x, y, paint );

            BitmapDescriptor icon = BitmapDescriptorFactory.FromBitmap( bitmap );

            return ( icon );
        }

Build, run and... again, out of memory...
07-04 15:36:42.175: E/mono(6409): Java.Lang.OutOfMemoryError: Exception of type 'Java.Lang.OutOfMemoryError' was thrown.
07-04 15:36:42.175: E/mono(6409): at Android.Runtime.JNIEnv.CallObjectMethod (intptr,intptr,Android.Runtime.JValue[]) <0x00080>
07-04 15:36:42.175: E/mono(6409): at Android.Graphics.Bitmap.Copy (Android.Graphics.Bitmap/Config,bool) <0x00167>
07-04 15:36:42.175: E/mono(6409): at MapsExtensionsTest.MainActivity.GetCustomBitmapDescriptor (string) <0x00097>
07-04 15:36:42.175: E/mono(6409): at MapsExtensionsTest.MainActivity.button_Click (object,System.EventArgs) <0x00187>
07-04 15:36:42.175: E/mono(6409): at Android.Views.View/IOnClickListenerImplementor.OnClick (Android.Views.View) <0x0005b>
07-04 15:36:42.175: E/mono(6409): at Android.Views.View/IOnClickListenerInvoker.n_OnClick_Landroid_view_View_ (intptr,intptr,intptr) <0x0005b>
07-04 15:36:42.175: E/mono(6409): at (wrapper dynamic-method) object.8ea9efe6-5373-404e-9980-0a2a1ca9397f (intptr,intptr,intptr) <0x00043>
07-04 15:36:42.175: E/mono(6409):   --- End of managed exception stack trace ---
07-04 15:36:42.175: E/mono(6409): java.lang.OutOfMemoryError
07-04 15:36:42.175: E/mono(6409): 	at android.graphics.Bitmap.nativeCopy(Native Method)
07-04 15:36:42.175: E/mono(6409): 	at android.graphics.Bitmap.copy(Bitmap.java:403)
07-04 15:36:42.175: E/mono(6409): 	at mono.android.view.View_OnClickListenerImplementor.n_onClick(Native Method)
07-04 15:36:42.175: E/mono(6409): 	at mono.androi

What can I do to avoid this? If the Google maps can do this when using the resource id directly, there has to be a way, right?...


UPDATE:

I went on all the trouble to replicate the application with Java using Android Studio. I was able to add all the 900 markers with custom bitmap without a problem... I was even able to add 900 more (1800 total) without any stress. Fast on add, fast on map usage. So this is definitely on the Xamarin - JVM bridge... I can't add half of the intended with Xamarin and the map gets slower very fast.


I tried to dispose the bitmaps correctly. It did no effect. Tried using Recycle, Dispose and nulling the instance. Nothing. Tried using GC.Collect, still didn't work, just took longer to crash because the garbage collector is slowing things down by running constantly. Other alternatives are welcome.

I am providing two projects; one built with C# and Xamarin.Android, the other one with Java under Android Studio. The same algorithm runs without a problem in Java. No memory problems, no map slowing down, all but good things. I loaded up to 2700 markers using the same code and without a single issue. I could probably add 900 more, didn't feel the need to try. Now, this same algorithm in C#, burns out on the first 900 markers with an out-of-memory.
Comment 3 Goncalo Oliveira 2013-07-08 10:05:39 UTC
Microsoft Visual Studio Premium 2012
Version 11.0.60610.01 Update 3
Microsoft .NET Framework
Version 4.5.50709

Xamarin.Android   4.6.08007 (0cc7ae3b)
Visual Studio plugin to enable development for Xamarin.Android.

Xamarin.iOS   1.1.200 (7d63692c)
Visual Studio extension to enable development for Xamarin.iOS
Comment 4 Jonathan Pryor 2013-07-10 16:06:41 UTC
Thank you for the test case.

I enabled GREF logging:

    adb shell setprop debug.mono.log gref

Ran the app on a Nexus 10, click "Hello World, Click Me!", and...

> I/monodroid-gref(26500): -w- grefc 2733 gwrefc 0 handle 0x1d200e5f/W from take_global_ref_jni

2733 GREFs for your 900 Bitmaps.

Ouch.

Note: this isn't identical to your app, as your project had custom keytool signing information that I don't have access to. Consequently when I run the app I don't see the map at all, so visually it's "broken", but that shouldn't alter the GREF count significantly.

Click the button again, get some coffee, and GREF use is in excess of 3700 GREFs, which is lower than I'd expect...but the GC is working overtime, and I got that count before it had even finished. (I waited several minutes as it was, and I didn't wait for the processing from the 2nd button click to complete before continuing...)

The task at hand, then, is to reduce the GREF count.

> I tried to dispose the bitmaps correctly. It did no effect.

Bitmaps are not the only instances you're creating that you don't actually need. You're creating two other things of significance:

* 900 LatLng instances, each of which will consume a GREF.
* Canvas instances, e.g. in your GetCustomBitmapDescriptor() method.

Ensuring that these are disposed drops me down to 1832 GREFS.

I consider this to be far too high. Read the GREF log from the beginning, and I notice that each time a LatLng instance is processed the GREF count keeps edging higher.

I use the IDE to hover over methods which don't have their return value captured to see if they're returning anything; this is in fact happening with `map.AddMarker()`:

    map.AddMarker( iconMarkerOption );

So I dispose of that, and now my GREF count is down to a far more reasonable 32.
Comment 7 Jonathan Pryor 2013-07-10 17:41:44 UTC
Interestingly...now that I've left it running for awhile, my GREF count is still 32 but I'm seeing the "Memory is critically low" Toast.

However, that's sensible; `adb logcat` contains:

> D/dalvikvm(27200): GC_EXPLICIT freed 41K, 3% free 112809K/115716K, paused 3ms+17ms, total 93ms

so it's using 112MB... Ouch.