Androidにおけるメモリリーク
Javaはメモリリークしない。(過去記事を参照)オブジェクトが相互に参照しあっていても Mark and Sweep によってメモリからクリアされるからだ。
よって、Java、そしてAndroidにおけるメモリリークとは、ライフサイクルの不一致のことを指す。
一般的に、リークするとまずいのは画像などであるが、Androidの場合はActivity(or Fragment)という「画面」も該当する。なぜなら、たいていの画面はたくさんの画像を保持しているからだ。
Androidでは気をつけないとActivityがリークする。今回はActivityのリークを調べる方法をご説明しよう。
Android Studioを使ってActivityのメモリリークを調べる
Android Studio の Run > Profile… をクリックすると、apkがdeviceにインストールされ、Android Profiler が立ち上がる。
Android Profiler に3つのグラフが表示される。これらはリアルタイムで更新される。MEMORY のグラフをクリックして、メモリ使用量のグラフのみを表示する。
Activity間の遷移を行ったり、Activityでの操作を行ったりしたあと、Backキーによってアプリを閉じる。
Android Profiler の左上にあるゴミ箱アイコンを何回か時間を置いてクリックする。この操作によって、参照がないオブジェクトはGCによってメモリから消える。メモリ使用量のグラフががくっと下がるのがわかるはずだ。ゴミ箱を押してもグラフに変動がなくなったら、次のステップに進もう。
ゴミ箱の隣の Dump Java Heap をクリックし、処理が終わるまでしばらく待とう。グラフの下にオブジェクトが列挙される。これらが、メモリに残っているものだ。
Activityは全て閉じた状態でGCを走らせた。そのため、Activityがメモリに残っている場合はリークしている。Activityをアプリのパッケージ名配下 com.example.ui
に置いているならば、Arrange by class
となっているプルダウンメニューを箇所を Arrange by package
に変更しよう。ここで、com
> example
> ui
とたどっていって、Activityクラスの名前があればリークしている。みつけたらクリックしよう。
Android Studio を使ってActivityのメモリリークを解決する
みつけたActivityクラスをクリックすると、 Instance View
が開く。ここには、そのクラスのメモリ上にあるインスタンスが列挙される。3つあれば、そのActivityが3つリークしているということだ。そのうちの1つをクリックしよう。
References
が開く。ここには、そのインスタンスへの参照が列挙される。何かがActivityへの参照を保持しているために、GCによってもメモリから削除されずにリークしている。解決するためには、この参照を切る必要がある。
しかし、Activityへの参照はたくさんある。なぜなら、Activity内で表示するViewは、ActivityをmContext
のようなメンバ変数で保持しているためだ。これは大抵の場合は問題ではない。なぜならActivity-View間の相互参照であり、JavaのMark and Sweep方式ではメモリからクリアされるためだ。問題となるのは、ROOTにつながる参照である。
ROOTにつながる参照を探すには、References
の Depth
カラムに着目する。Depthが小さくなるほどROOTに近い。そのためDepthの昇順でソートして(デフォルト)、上から順に展開していき、Depth 0 までの参照をたどる。
Depth 0 までの参照がわかれば、その参照を切ればよい。なぜそのような参照が残ってしまうのかは、一般的なバグ解決の手段である(がんばれ)。これでメモリリークは解決だ。
注意
static変数
Activityが全て閉じられても、プロセスが死ぬとは限らない。もっとも、あなたがProfilerで見れている以上はそのプロセスは生きている。プロセスが生きていれば、staticな変数などはメモリ上に残っていて正常だ。
WeakReference
WeakReference
のインスタンスからの参照は、メモリリークにつながらない。原因は別の参照経路にある。なぜなら WeakReference
は他からの参照がある場合のみ参照を保持し続けるという「弱参照」をするためのクラスであるからだ。
this$0
this$0
から参照されている場合、実際のコードを探してもそのような定義文は存在しない。これは、staticでないインナークラスが、アウタークラスへの参照を暗黙のうちに保持しているためだ。Android開発におけるあるあるメモリリークパターンである。 ref https://blog.kengo-toda.jp/entry/20091112/1258033092