Androidアプリのメモリリークを調査する
Androidアプリを開発していると、たまにOutOfMemoryに遭遇する。そういう時に便利な、メモリリークの調査方法を紹介する。
以下のサイトを参考にさせてもらった。
上記のサイトは、手順がわかりにくい箇所もあったので、補足の意味で書こうと思う。
メモリリークを調査するには?
メモリリークを調査する、と言われても、なかなかピンとこない。しかし、やっていることは簡単。
- アプリケーションを操作する
- ガベージコレクション(GC)を走らせる
- ヒープメモリの状態を取得
- ヒープメモリを見る
- ガベージコレクション(GC)で回収できていないメモリを探す
という5つの手順だけ。これで回収できていないメモリがあれば、回収できるように参照を消したりする必要がある(ここが難しい)。
アプリケーションを操作する
エミュレータ上でアプリケーションを動かす。アプリケーションを操作する前に、DDMSのUpdate Heapボタンを押しておく。
緑色のタンクみたいなアイコンのボタンが、Update Heapボタン。
アプリケーションをいろいろと動かしてみる。調査したい機能が決まっているならば、その機能だけを動かすのもあり。
画像を使っている箇所は、メモリ回収できないと、OutOfMemoryが出る可能性があるので、入念にチェックしたい。
一通りの操作が終わったら、アプリケーションを閉じる。アプリケーションが前面に出ていないように。メイン画面まで戻って、Homeボタンを押す。1機能の調査ならば、その機能を終了させる。
ガベージコレクション(GC)を走らせる
ガベージコレクション(GC)を走らせるのもボタン1つ。
Cause GCボタンを押すだけ。いつも3回ぐらい連打している。
ヒープメモリの状態を取得
ヒープメモリの状態を取得するには、Dump HPROF fileボタンを押す。
これでヒープメモリの情報がファイルで出力される。
HPROFは、Java標準のプロファイラ。そのプロファイラで解析する。
ヒープメモリを見る
hprof-convコマンドを使って、標準のhpof形式のファイルを作成する。
[java]
hprof-conv net.eikatou.xx.hprof xx.hprof
[/java]
hprof-convコマンドは、Android SDKに入っている。「/android-sdk-mac_x86/platform-tools/hprof-conv」。
出来上がったhprofファイルをjhatコマンドに渡す。
[java]
jhat xx.hprof
[/java]
jhatはJavaのSDKに入っている。
jhatコマンドを実行後に「http://localhost:7000/」にアクセスすると、ヒープの内容が表示される。
ガベージコレクション(GC)で回収できていないメモリを探す
ヒープの内容が表示されたら、自分のクラスの中身を確認する。
自分のクラスのページを開いて、さらに下にスクロール。
「References to this object」にリンクが入っていると、メモリにインスタンスが残っている。このActivityはDestoryされていた。GCで回収されないといけないはずなのに、メモリに残っている。
逆に、実行中のクラスは表示されるのが正常である。Activityは画面上から消えていても、実行中の状態であることが多いので注意が必要。
メモリに残っているという事は、参照が残っているということ。どこから参照されているのかが問題となる。そういう時は、すぐ下にある「excludes weak refs」のリンクをクリック。
今回は、「net.eikatou.ib.frame.ChoiceAction.installedAppSyncTask」のフィールド「activity」に参照が残っていた。
Javaのソースコードを変更して、activityの保持を止めたら、GCで回収されるようになった。
今回はActivityを確認したが、Activityで保持しているインスタンスが回収されていることを確認するのも良いだろう。例えば、「Activityでimageを保持している。Activity#onStopでimageの参照を破棄する。その後のGCで、imageのインスタンスが回収されていることを確認する。」といった具合に。
ポイント
以下のリンクを参考。注意して実装しましょう。
Contextに注意
よく引っかかるのが、以下のようなソースコード。
[java]
new ArrayAdapter
[/java]
これは、ArrayAdapterの第1引数のContextに、自分のクラスを渡している。ArrayAdapterは、このContextを保持してしまうため、参照が残り続ける。
[java]
new ArrayAdapter
[/java]
getApplicationContext()を使うようにすると良い。
ついついContextを持ち回ってしまうため、あちこちに参照が残る事となる。Contextは要注意だと覚えておくと良い。