Mono for Androidはメモリリークが多すぎる?
GREF has increased to 2001
が表示される事が多くなった。
これはJNIのグローバルリファレンス(オブジェクトの管理テーブル)の数が制限値の2000*1を超えたので表示されている。
原因はインスタンス化したBitmapやWidgetを明示的にDisposeしていないのが原因らしい。
例えば以下の様にすると容易くエラーを起こすことができる。
for (int i = 0; i < 2000; i++) { TextView v = new TextView (context); v.Text = i.ToString (); }
ではDisposeすれば良いのかというと、そうでは無かった。以下の様にすると相変わらずエラーがでる。
for (int i = 0; i < 2000; i++) { TextView v = new TextView (context); v.Text = i.ToString (); v.Click += HandleVClick; // <- Clickイベントハンドラにハンドラを登録 v.Click -= HandleVClick; // <- Clickイベントハンドラからハンドラを解除 v.Dispose (); }
Clickイベントハンドラは内部でIOnClickListenerImplementorクラスをインスタンス化している。しかしViewクラス(TextViewの基底クラス)はこれをDisposeしてくれないのだ。
Clickイベントハンドラは内部は完全にprivateになっているので外側から手を出すことができない。リフレクションで無理矢理Disposeすることが可能だがあまり気が進まない。
for (int i = 0; i < 600000; i++) { //<-2000を超えても大丈夫 TextView v = new TextView (context); v.Text = i.ToString (); v.Click += HandleVClick; Type ff = typeof(Android.Views.View); System.Reflection.PropertyInfo pi = ff.GetProperty("ImplClick",System.Reflection.BindingFlags.Instance |System.Reflection.BindingFlags.NonPublic); Java.Lang.Object obj = (Java.Lang.Object)pi.GetValue(v, null); obj.Dispose (); v.Dispose (); }
似たような問題をJavaListクラスでも見つけている。こっちはJavaObjectクラスを内部でインスタンス化しているのにちっともDisposeしてくれない。
※Mono for Androidのバージョンはv1.9.1である。
※強制GCはコストが高すぎるので使いたくない。
追記1
バージョンv1.9.2でも同じ。
強制GCでエラー回避は可能?
以下のパターンは完全にDisposeしてくれた。
for (int i = 0; i < 2000; i++) { TextView v = new TextView (context); v.Text = i.ToString (); v.Click += HandleVClick; } GC.Collect ();
以下のパターンはDisposeが不完全。500個のJavaObjectが残ってしまう。
class MyClass{} ////////// { JavaList<MyClass> aa = new JavaList<MyClass>(); for (int i = 0; i < 500; i++) { aa.Add(new MyClass()); } ArrayAdapter<MyClass> ad = new ArrayAdaptor<MyClass> (context, 0 ,aa); } GC.Collect ();
*1:実機だと52000