內存泄露檢測工具LeakCanary使用詳解

序言:

你是否曾被OOM問題困擾過?對絕大部分客戶端開發人員來講,內存溢出問題如同幽靈通常如影相隨,平時也許不必定容易察覺,可一旦發生了真想把它揪出來時,又只能抓耳撓腮不得其蹤。 然而幸運的是,開源屆的良心企業Square推出了LeakCanary,這是一個用來檢查 Android 下內存泄漏的開源庫,從而幫助咱們可以以簡潔明瞭的方式定位內存泄露隱患。這篇文章主要介紹其用法、架構和其背後的實現原理。html

Square 有篇文章介紹了開發這個庫的緣由。他們的一個付款流程裏,他們直接用 Bitmap 來畫用戶簽名,Bitmap 大小和屏幕分辨率是同樣的。問題來了,在試圖建立這個 Bitmap 對象時,常常會引起OutOfMemoryError。他們嘗試了一些解決方案,但都沒解決問題:java

  • 使用 Bitmap.Config.ALPHA_8 由於,簽名僅有黑色。
  • 捕捉 OutOfMemoryError異常, 嘗試 GC 並重試(受 GCUtils 啓發)。
  • 咱們沒想過在 Java heap 內存以外建立 bitmap 。苦逼的咱們,那會 Fresco 還不存在

最終這些都不起做用,他們才發現走錯了方向。當存在內存泄漏時,可用內存愈來愈少,這個時候 OOM 能夠發生在任何地方,特別是試圖建立一些大內存對象,如 Bitmap 的時候。android

雖然咱們能夠經過MAT對內存泄漏問題進行分析,可是畢竟步驟繁瑣,還不必定能從茫茫對象中定位到異常發生現場。若是有一個工具能自動完成這些事情,甚至在發生 OOM 以前,就把內存泄漏報告給你,豈不美哉?LeakCanary 就是用來幹這個事情的。在測試你的 App 時,若是發生了內存泄漏,狀態欄上會有通知,同時在logcat 上也會有相應的 log。git

一. 使用方法

1. 監控 Activity 泄露

咱們常常把 Activity 看成爲 Context 對象使用,在不一樣場合由各類對象引用 Activity。因此,Activity 泄漏是一個重要的須要檢查的內存泄漏之一。github

public class ExampleApplication extends Application {

    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context.getApplicationContext();
        return application.refWatcher;
    }

    private RefWatcher refWatcher;

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

LeakCanary.install()返回一個配置好了的 RefWatcher 實例。它同時安裝了 ActivityRefWatcher 來監控 Activity 泄漏。即當 Activity.onDestroy() 被調用以後,若是這個 Activity 沒有被銷燬,logcat 就會打印出以下信息告訴你內存泄漏發生了。架構

* com.example.leakcanary.MainActivity has leaked:
    * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1')
    * references com.example.leakcanary.MainActivity$2.this$0 (anonymous class extends android.os.AsyncTask)
    * leaks com.example.leakcanary.MainActivity instance
    * Reference Key: c4d32914-618d-4caf-993b-4b835c255873
    * Device: Genymotion generic Google Galaxy Nexus - 4.2.2 - API 17 - 720x1280 vbox86p
    * Android Version: 4.2.2 API: 17
    * Durations: watch=5100ms, gc=104ms, heap dump=82ms, analysis=3008ms


LeakCanary 自動檢測 Activity 泄漏只支持 Android ICS 以上版本。由於 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。若是要在 ICS 以前監測 Activity 泄漏,能夠重載 Activity.onDestroy() 方法,而後在這個方法裏調用 RefWatcher.watch(this) 來實現。app

2. 監控 Fragment 泄漏

public abstract class BaseFragment extends Fragment {

    @Override 
    public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
    }
}

當 Fragment.onDestroy() 被調用以後,若是這個 fragment 實例沒有被銷燬,那麼就會從 logcat 裏看到相應的泄漏信息。ide

3. 監控其餘泄漏

...
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(someObjNeedGced);

當 someObjNeedGced還在內存中時,就會在 logcat 裏看到內存泄漏的提示。函數

4. 集成 LeakCanary 庫

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

在 debug 版本上,集成 LeakCanary 庫,並執行內存泄漏監測,而在 release 版本上,集成一個無操做的 wrapper ,這樣對程序性能就不會有影響。工具

二. 實現原理

1. LeakCanary 流程圖

LeakCanary 的機制以下:

  1. RefWatcher.watch()會以監控對象來建立一個 KeyedWeakReference弱引用對象
  2. 在 AndroidWatchExecutor的後臺線程裏,來檢查弱引用已經被清除了,若是沒被清除,則執行一次 GC
  3. 若是弱引用對象仍然沒有被清除,說明內存泄漏了,系統就導出 hprof 文件,保存在 app 的文件系統目錄下
  4. HeapAnalyzerService啓動一個單獨的進程,使用 HeapAnalyzer來分析 hprof 文件。它使用另一個開源庫 HAHA
  5. HeapAnalyzer經過查找 KeyedWeakReference弱引用對象來查找內在泄漏
  6. HeapAnalyzer計算 KeyedWeakReference所引用對象的最短強引用路徑,來分析內存泄漏,而且構建出對象引用鏈出來。
  7. 內存泄漏信息送回給 DisplayLeakService, 它是運行在 app 進程裏的一個服務。而後在設備通知欄顯示內存泄漏信息。

2. 源碼分析

(1). 如何導出 hprof 文件

@Override public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if (!waitingForToast.wait(5, SECONDS)) {
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }

能夠參閱 AndroidHeapDumper.java 的代碼。

(2). 如何分析 hprof 文件

這是個比較大的話題,感興趣的能夠移步另一個開源庫 HAHA,它的祖先是 MAT

(3). 如何使用 HandlerThread

能夠參閱 AndroidWatchExecutor.java的代碼,特別是關於 Handler, Loop 的使用。

(4). 如何判斷某個變量是否被已經被 GC 回收

能夠參閱 RefWatcher.java 的 ensureGone() 函數。最主要是利用WeakReferenceReferenceQueue 機制。簡單地講,就是當弱引用 WeakReference 所引用的對象被回收後,這個 WeakReference 對象就會被添加到 ReferenceQueue 隊列裏,咱們能夠經過其 poll() 方法獲取到這個被回收的對象的 WeakReference 實例,經過判斷其是否爲 null 進而知道須要監控的對象是否被回收了。

相關文章
相關標籤/搜索