Bug 1084 - Memory Leak/Application crash when using the SensorEventListener
Summary: Memory Leak/Application crash when using the SensorEventListener
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 1.0
Hardware: PC Windows
: High major
Target Milestone: ---
Assignee: Bugzilla
Depends on:
Reported: 2011-09-27 13:16 UTC by dellis1972
Modified: 2012-01-09 09:57 UTC (History)
5 users (show)

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

Modified GLView with Accelerometer (4.23 KB, text/plain)
2011-09-27 13:16 UTC, dellis1972
Log file of memory usage and exception (4.60 KB, text/plain)
2011-09-27 13:17 UTC, dellis1972

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:

Description dellis1972 2011-09-27 13:16:13 UTC
Created attachment 509 [details]
Modified GLView with Accelerometer

The Attached File is a modification of the Default OpenGL template craeted in VS 2010. The logging of GC.GetTotalMemory(false) is reporting a steady climb in memory usage. 

This eventually results in the following exception

I/MonoGameInfo(15504): System.NullReferenceException: Object reference not set t
o an instance of an object
I/MonoGameInfo(15504):   at Android.Runtime.JNIEnv.NewGlobalRef (IntPtr jobject)
 [0x00000] in /home/jpobst/Desktop/monodroid/Mono.Android/src/Runtime/JNIEnv.cs:
I/MonoGameInfo(15504):   at Java.Lang.Object.RegisterInstance (IntPtr value, IJa
vaObject instance, Boolean owned) [0x0003c] in /home/jpobst/Desktop/monodroid/Mo
I/MonoGameInfo(15504):   at Java.Lang.Object.SetHandle (IntPtr value, Boolean ow
ned) [0x00000] in /home/jpobst/Desktop/monodroid/Mono.Android/src/Java.Lang/Obje
I/MonoGameInfo(15504):   at Java.Lang.Object..ctor (IntPtr handle) [0x00023] in
I/MonoGameInfo(15504):   at Android.Runtime.JavaArray`1[System.Single]..ctor (In
tPtr handle) [0x00000] in <filename unknown>:0
I/MonoGameInfo(15504):   at Android.Hardware.SensorEvent.get_Values () [0x0002d]
 in /home/jpobst/Desktop/monodroid/Mono.Android/platforms/android-8/src/generate
I/MonoGameInfo(15504):   at OpenGLApplication1.Accelerometer+SensorListener.OnSe
nsorChanged (Android.Hardware.SensorEvent e) [0x00028] in C:\Users\Developer\Doc
uments\Visual Studio 2010\Projects\OpenGLApplication1\OpenGLApplication1\Activit

probably due to the system running out of memory.

This problem is holding back the latest release of the MonoGame framework for android as many games will require a working accelerometer to make them useful on the android platform.
Comment 1 dellis1972 2011-09-27 13:17:08 UTC
Created attachment 510 [details]
Log file of memory usage and exception
Comment 2 Atsushi Eno 2011-10-05 03:56:09 UTC
In the sample repro code, SensorListener should derive from Java.Lang.Object instead of implementing IJavaObject.Handle property to throw NotImplementationException.
seealso: http://docs.monodroid.net/index.aspx?link=T:Android.Runtime.IJavaObject

Now, I tried to reproduce this on ASUS Transformer/3.1. I could, only when it went to sleep mode. Until that, MonoGameInfo log showed unchanged memory usage (i.e. no increase).
Comment 3 dellis1972 2011-10-06 06:37:51 UTC
I changed the SensorListener to derive from Java.Lang.Object and removed the Handle property (I assume it is not needed)

However I still get a leak and an eventual crash, on both the Emulator and my Galaxy S. 

When the app crashes it usually puts the following in the output log

"In mgmain JNI_OnLoad
The program 'Mono' has exited with code 255 (0xff)."

The Galaxy S is running 2.3.3.

Any other suggestions/work arounds would be approciated.
Comment 4 Atsushi Eno 2011-10-06 10:19:24 UTC
from IRC:

> [23:12] <technomage> eno_ btw. in the example app I changed the call to GC.GetTotalMemory(false) to GC.GetTotalMemory(true) to force a GC and that seemed to fix the memory problem but introduced a definate hang in the app while the GC was collecting .

I'm CCing Rodrigo, maybe he has some insights.
Comment 5 dellis1972 2011-12-05 14:37:47 UTC
This does not appear to be fixed in M4A 4.0.
Comment 6 Jonathan Pryor 2012-01-06 10:51:22 UTC
You mention that it crashes on both the emulator and a Galaxy S. The emulator crash is because it's running out of GREFs:


When running on your Galaxy S, how long does it take to crash? When I run your app (modified as per comment #2) on a Nexus One, my memory usage is stable for several minutes, and it doesn't crash (though I may not have left it running long enough to crash):

> I/MonoGameInfo(22664): Memroy 48144

Later, though, memory usage balloons:

> I/MonoGameInfo(22664): Memroy 5619120

Quite a jump, and no intermediate values. No crash, but the memory jump is crazy. Where's the memory coming from? So let's break out the big guns:

> adb shell setprop debug.mono.log gref

What's going on?

> I/monodroid-gref(22696): +g+ grefc 7229 gwrefc 0 obj-handle 0x40517458/L -> new-handle 0x40517458/L from    at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)
> I/monodroid-gref(22696):    at Java.Lang.Object.SetHandle(IntPtr value, JniHandleOwnership transfer)
> I/monodroid-gref(22696):    at Java.Lang.Object..ctor(IntPtr handle, JniHandleOwnership transfer)
> I/monodroid-gref(22696):    at Android.Runtime.JavaArray`1[[System.Single, mscorlib, Version=, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]..ctor(IntPtr handle, JniHandleOwnership transfer)
> I/monodroid-gref(22696):    at Android.Hardware.SensorEvent.get_Values()
> I/monodroid-gref(22696):    at Scratch.SensorEventListenerLeak.Accelerometer+SensorListener.OnSensorChanged(Android.Hardware.SensorEvent e) in /Users/jon/Development/Projects/Scratch.SensorEventListenerLeak/GLView1.cs:line 114
> I/monodroid-gref(22696):    at Android.Hardware.ISensorEventListenerInvoker.n_OnSensorChanged_Landroid_hardware_SensorEvent_(IntPtr jnienv, IntPtr native__this, IntPtr native_e)
> I/monodroid-gref(22696):    at System.Object.89873e5a-a9f7-4061-bbd5-cd0b8c3f248a(IntPtr , IntPtr , IntPtr )

In short order, we have over 7000 GREFs created. No wonder it dies on the emulator.

The GREFs are coming from the access of `e.Values`, e.g.

    var x = e.Values[0];

This requires that we construct a JavaList<float> so that you can extract the values, and the JavaList<float> grabs a GREF. Furthermore, each `e.Values` access creates another JavaList<float>, so you're creating ~4 GREFs (for e.Values.Count and e.Values[0..3]). Since the OnSensorChanged callback is invoked frequently, the result is lots of GREFs in a short period of time.

So this isn't a GC bug, per-se, because the JavaList<float> instance is small, so the GC is more than happy to let tons of these instances be created before attempting to finalize them...


1. Add a GC.Collect() call to OnSensorChanged(). Problem: this causes performance to tank (though that might be because of all the additional `adb logcat` messages due to gref output). A GC.Collect() elsewhere might work as well; I haven't explored that option...

2. Dispose of the JavaList<float>, by changing the `e.Values[0]`/etc. lines to:

    if (e != null && e.Sensor.Type == SensorType.Accelerometer) {
        var values = e.Values;
        try {
            if (values != null && values.Count == 3) {
                var x = values[0];
                var y = values[1];
                var z = values[2];
                //Android.Util.Log.Info("MonoGameInfo", String.Format("{0} {1} {2}", x, y, z));
        } finally {
            IDisposable d = values as IDisposable;
            if (d != null)
                d.Dispose ();

This ensures that the JavaList<float> instance gets disposed of, releasing the GREF. The result of the above change is that the GREF output stabilizes at ~47 GREFs, which should certainly keep the emulator happy.

Closing as INVALID because the "GC doesn't run often enough" problem is "well-known", at least to some extent, and only indirectly related to the original bug report.