This is Xamarin's bug tracking system. For product support, please use the support links listed in your Xamarin Account.
Bug 1084 - Memory Leak/Application crash when using the SensorEventListener
Summary: Memory Leak/Application crash when using the SensorEventListener
Status: CLOSED INVALID
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries (show other bugs)
Version: 1.0
Hardware: PC Windows
: High major
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2011-09-27 13:16 UTC by dellis1972
Modified: 2012-01-09 09:57 UTC (History)
5 users (show)

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


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

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:
232
I/MonoGameInfo(15504):   at Java.Lang.Object.RegisterInstance (IntPtr value, IJa
vaObject instance, Boolean owned) [0x0003c] in /home/jpobst/Desktop/monodroid/Mo
no.Android/src/Java.Lang/Object.cs:103
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
ct.cs:91
I/MonoGameInfo(15504):   at Java.Lang.Object..ctor (IntPtr handle) [0x00023] in
/home/jpobst/Desktop/monodroid/Mono.Android/src/Java.Lang/Object.cs:48
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
d/Android.Hardware.SensorEvent.cs:67
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
y1.cs:57

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:

http://docs.xamarin.com/android/troubleshooting?highlight=gref#Unexpected_NullReferenceExceptions

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=2.0.5.0, 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...

Fixes/Workarounds:

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.

Note You need to log in before you can comment on or make changes to this bug.