LeakCanaryでメモリリークを検出する


LeakCanary

Squareがメモリリークを検出するライブラリ square/leakcanary を公開したので、さっそく使ってみたらすごく便利だった話です。

A small leak will sink a great ship

Piwaiが書いたLeakCanaryの記事がこちらです。
LeakCanary: Detect all memory leaks!

要約すると、

  • Squareではビットマップキャッシュに顧客の署名を書いていたが、端末の画面のサイズ分のメモリを確保するので、署名をするときにクラッシュすることがあり、それがOOMの大半を占めていた。
  • Bitmap.Configを変更したり、OOMをキャッチしてGCを走らせたりしたが、問題の解決には至らなかった。
  • 我々は間違ったアプローチを取っていたことに気が付いた。ビットマップの大きさではなくメモリリークが根本的な原因だったのだ。
  • 通常であればメモリリークを検出するのは手作業だが、これらをライブラリが自動で検出すればあなたはメモリリークの修正に集中することができる。そこでLeakCanaryというメモリリークを検出するライブラリを作った。
  • その結果は素晴らしく、実に94%のOOMのクラッシュを減らすことができた。

If you want to eliminate OOM crashes, install LeakCanary now!

LeakCanaryの導入の仕方

Dependencyにleakcanaryを追加して、

dependencies {
 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}

アプリケーションの初期化時にLeakCanaryを初期化するだけです。

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

リポジトリにサンプルプロジェクトがあるので、試してみることができます。

$ git clone [email protected]:square/leakcanary.git
$ cd leakcanary/library
$ ./gradlew leakcanary-sample:installDebug

メモリリークが発生すると通知が表示されて、タップするとActivityが開いて誰がどのオブジェクトを持っているのかを見ることができます。

さらに、メモリリークの通知はアプリとしてインストールされるので、いつでも確認できるようになります。これはサンプルではなくライブラリの機能なので、一行あなたのプロジェクトに追加するだけで、この便利な機能を使うことができます。

LeakCanaryのメモリリークの検出方法

なぜ一行書くだけでメモリリークを検出できるのか

  • LeakCanary.install(application) を呼ぶ。
  • ActivityRefWatcherクラスによってActivityLifecycleCallbackに登録される。
  • ActivityがonDestroyされる度にGCを走らせる。
  • 参照が解放されたかチェックして、されていなければヒープをダンプして解析してDisplayLeakActivityに表示する。

ヒープ情報はどこから来るのか

Debug.dumpHprofData(fileName) を呼んでいます。
hprofはバイナリなのですが org.eclipse.mat のパーサーを使ってSnapshotに変換して、そこから表示しやすくするためにLeakTraceに変換しています。

実際に使ってみた

さっそくアプリに入れてみたらメモリリークが!LeakCanaryを見てみると、破棄されるはずのActivityが参照を持っていて、調べるとあるFragmentの中でBusのunregisterが漏れていることが分かりました。

一行入れるだけで簡単にメモリリークが調べられてすごく良いです。優秀なSquareのエンジニアでさえメモリリークは見逃してしまうものなので largeHeap を有効にする前にまずLeakCanaryを入れましょう。