moxt

Just another Blog site

LeakCanaryの仕組みをある程度理解したいマン

   

LeakCanaryとは何か

https://corner.squareup.com/2015/05/leak-canary.html

Square社が開発したメモリリーク検知ライブラリ。
導入はとてもシンプル。

これだけでメモリリークが発生したときに通知してくれる。便利。

では、メモリリークの検知をどのように実現しているのだろうか。
気になったので読んでみた。

どのタイミングでメモリリークの有無の確認を実行しているのか?

カギはActivityRefWatcherにあった。

https://github.com/square/leakcanary/blob/master/leakcanary-android/src/main/java/com/squareup/leakcanary/ActivityRefWatcher.java

ActivityLifecycleCallbacksをApplicationに登録することでアプリ内に登録されてるActivityのonXXXをListenできるようになる。

↑を見てみると、Activityが破棄されたタイミングでRefWatcherクラスのwatchメソッドが呼ばれている。

画面が破棄されたタイミングでメモリリークが無いかwatchして、メモリリークが確認できれば通知する。
っていう流れだろうか。

詳しく見てみる。

RefWatcherクラスの役割

https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/RefWatcher.java

watch

watchではどんな処理が実行されているのだろうか。

ensureGoneメソッドで与えられた参照がGCによって葬り去れるか調べてる感じ。
メインスレッドで実行するとブロッキングが発生するのでwatchExecutorが専用のThread上でensureGoneしてる。

KeyedWeakReferenceクラスが活躍するのはこれから。
このクラスのおかげで与えられた参照がGCされたかどうか検知できるようになる。
その辺は後ほど。

とりいそぎ、ensureGoneメソッドを見る。

ensureGone

ざっくりと流れは

  • GCを実行する
  • removeWeaklyReachableReferences()を実行して、与えられた参照がGCによって回収されているか確認しつつ、参照のキーを保持してるSetクラス変数retainedKeysからキーを抹消する(メソッド名と実際の処理内容のマッチングが微妙な感じではあるが。。)
  • retainedKeysに該当するキーが抹消されてるか確認して、残っていればGCで回収できない=誰かに参照されてる=メモリリークと判断する
  • メモリリークと判断されたらヒープをダンプして通知UIやら詳細情報表示する用Activityを表示する処理を実行する

と、こんな感じ。
上記の処理をあるActivityがDestroyされるたびに実行してる。
なんとなく流れは分かった気分になってる。

removeWeaklyReachableReferencesでKeyedWeakReferenceクラスが活躍する。

https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/KeyedWeakReference.java

↑の実装を見つつ、こちらの記事を見ると大変分かりやすい。

http://www.ne.jp/asahi/hishidama/home/tech/java/weak.html

複数のWeakReferenceを使う場合、どれがクリアされたかを知るには、全WeakReferenceをリストか何かに入れておいて一つ一つget()してnullが返ってくるかどうかをチェックすればいいような気がする。
しかしこれは無駄が多い。

そこで、もう少しエレガントな方法が用意されている。
参照キュー(ReferenceQueueクラス)というものを用意し、WeakReferenceのコンストラクターに渡す。
すると、WeakReferenceから値が消されると、参照キューにそのWeakReferenceが追加される。

これを踏まえて、再びremoveWeaklyReachableReferencesを見てみる

WeakReferenceを作るときにReferenceQueueを渡しておくと、GCが実行されたときにReferenceQueueに参照(ここでいうとKeyedWeakReference)が格納される。
で、queueのpollメソッドを呼び出すとqueueに詰まってる参照がとりだせる。
とりだせない、ということは参照先のオブジェクトに対してGCができなかった。ということ。

別のオブジェクトが参照先のオブジェクトを参照している=メモリリーク、なので集合retainedKeysからキーを取り除けないので、goneでfalseを返す。で、メモリリーク時の処理が実行される。

だいたいこんな感じ。

 - Android

  • このエントリーをはてなブックマークに追加
  • follow us in feedly