Leakcanary淺析

本文主要分析內(nèi)存泄漏的檢測原理和如何實現(xiàn)生產(chǎn)環(huán)境應(yīng)用,代碼分析基于Leakcanary 1.6版本。

如何檢測內(nèi)存泄漏

要想搞懂如何檢測內(nèi)存泄漏,有一個基本知識點需要知道:

引用類型 被垃圾回收時間 用途 用途
強引用 從來不會 對象的一般狀態(tài) JVM停止運行時終止
軟引用 當(dāng)內(nèi)存不足時 對象緩存 內(nèi)存不足時終止
弱引用 正常垃圾回收時 對象緩存 垃圾回收后終止
虛引用 正常垃圾回收時 跟蹤對象的垃圾回收 垃圾回收后終止

這里我們需要用到弱引用,因為弱引用符合我們檢測的場景,就是當(dāng)內(nèi)存充足的時候觸發(fā)的系統(tǒng)gc,弱引用對象也能被回收,這樣有利于我們觀察對象回收情況,并且弱引用在初始化的時候,有如下構(gòu)造方法:

WeakReference(T referent, ReferenceQueue<? super T> q)

其中第二個參數(shù)是個隊列,當(dāng)對象被回收后會被放入該隊列中,我們就可以通過在activity或者fragment onDestroy的時候,主動觸發(fā)系統(tǒng)gc,然后查看隊列中是否有該對象,如果沒有,我們就認(rèn)為發(fā)生了一次內(nèi)存泄漏。我們看下leakcanry的具體實現(xiàn):

public static void install(Context context, RefWatcher refWatcher) {
  Application application = (Application) context.getApplicationContext();
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

  application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
  @Override public void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }
};

通過application的registerActivityLifecycleCallbacks監(jiān)聽所有activity的生命周期。接下去再看下在onActivityDestoryed中做了什么:

public void watch(Object watchedReference, String referenceName) {
    .....
    final long watchStartNanoTime = System.nanoTime();
    // 隨機生成一個key
    String key = UUID.randomUUID().toString();
    // key放入Set<String>
    retainedKeys.add(key);
    // new 一個包含key信息的弱引用對象
    final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);

    // 異步監(jiān)測
    ensureGoneAsync(watchStartNanoTime, reference);
  }
// 啟動異步監(jiān)測
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

    // 更新retainedKeys,把存在queue中回收掉的key在retainedKeys去除掉
    removeWeaklyReachableReferences();

    // 如果當(dāng)前應(yīng)用的key已經(jīng)不在retainedKeys中,說明引用在之前就被gc了
    if (gone(reference)) {
      return DONE;
    }
    // gc
    gcTrigger.runGc();
    // 再次更新retainedKeys
    removeWeaklyReachableReferences();
    // 這個時候引用的key還在retainedKeys,說明引用已經(jīng)內(nèi)存泄漏
    if (!gone(reference)) {
      ......dump操作
    }
    return DONE;
  }

以上是acitivty的監(jiān)測方法,接下去我們看下fragment的相關(guān)流程。

// 1、install
public static void install(Context context, RefWatcher refWatcher) {
    List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

    if (SDK_INT >= O) {
      // os包下面的Fragment
      fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
    }

    try {
      // v4包下面的Fragment,這邊用反射因為類放在另外一個module下
      Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
      Constructor<?> constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
      FragmentRefWatcher supportFragmentRefWatcher = (FragmentRefWatcher) constructor.newInstance(refWatcher);
      fragmentRefWatchers.add(supportFragmentRefWatcher);
    } catch (Exception ignored) {
    }

    if (fragmentRefWatchers.size() == 0) {
      return;
    }

    Helper helper = new Helper(fragmentRefWatchers);

    Application application = (Application) context.getApplicationContext();
    // 添加Activity的生命周期回調(diào)
    application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}

// 2、在activity的onActivityCreated生命周期調(diào)用的時候添加檢測fragment邏輯
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
    @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    // 遍歷os包和v4包下所有的watcher
    for (FragmentRefWatcher watcher : fragmentRefWatchers) {
      // 在onActivityCreated的時候給這個activity添加檢測fragment邏輯
      watcher.watchFragments(activity);
    }
  }
};

// 3、給activity添加檢測fragment的邏輯
@Override public void watchFragments(Activity activity) {
  FragmentManager fragmentManager = activity.getFragmentManager();
  // 添加遞歸回調(diào)
  fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}

// 4、 在fragment的生命周期中檢測
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() {
  @Override
  public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
    // 在fragment的生命周期中檢測
    refWatcher.watch(fragment);
  }
};

接下去具體的watch邏輯就和activity的一模一樣,這里就不多贅述了。

生產(chǎn)環(huán)境應(yīng)用

如果把leakcanary直接搬到線上的話有有以下幾個問題需要解決:

  • dump出來的文件有100M左右甚至更大
  • Debug.dumpHprofData(heapDumpFile.getAbsolutePath());dump方法會凍結(jié)應(yīng)用所有進程,持續(xù)數(shù)秒到數(shù)十秒的卡頓

基于以上兩個問題,我們可以參考下幾家大廠的解決方案:

微信開源的 Martix 中的Matrix-Android-ResourceCanary

Martix 把分析過程需要用到的部分字符串?dāng)?shù)據(jù)和Bitmap的buffer數(shù)組外,其余的buffer數(shù)據(jù)都直接剔除,這樣處理之后的Hprof文件通常能比原始文件小1/10以上。

其他主要增加了一些誤報的優(yōu)化。

美團的 Probe:Android線上OOM問題定位組件

通過 native hook 了虛擬機寫入 hprof 文件的操作,在這里先過濾了某些數(shù)據(jù),最終生成的 hprof 文件就是裁剪后的了。在不考慮可能會有的兼容性問題,這個方案要比其他的方案要高效,因為它解決了 hprof 加載的內(nèi)存問題。

但是Probe是閉源的。

快手的 KOOM——高性能線上內(nèi)存監(jiān)控方案

快手最近開源的KOOM有這幾個亮點:

  • 實現(xiàn)了Probe中提出 hook 生成 hprof 的 native 方法
  • 解決了 dumpHprofData 方法阻塞問題
  • 使用了 LeakCanary2 中使用的 hprof 分析庫 shark

對于 dumpHprofData 方法阻塞問題,koom的解決方式是fork新進程來執(zhí)行dump操作,這樣對父進程的正常執(zhí)行沒有影響。以下是耗時對比:

fork dump normal dump
耗時(ms) 139.5 19962.5

綜上對比,只有koom解決了線上應(yīng)用的兩大痛點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • LeakCanary原理淺析 1.LeakCanary簡介 LeakCanary是一個Android和Java的內(nèi)...
    chewbee閱讀 7,053評論 2 38
  • 在Android開發(fā)中最讓人們頭疼的就是內(nèi)存泄漏了,今天來介紹一個查看內(nèi)存是否泄漏的工具LeakCanary,并通...
    猩程變閱讀 383評論 1 0
  • LeakCanary 是由 Square 開源的針對 Android 和 Java 的內(nèi)存泄漏檢測工具。 使用 L...
    小小的coder閱讀 406評論 0 1
  • 說到內(nèi)存泄漏,就必須提到LeakCanary. 這個利器,很方便的顯示出內(nèi)存泄漏的地方。在用到的過程中好奇怎...
    David_zhou閱讀 1,392評論 0 0
  • 前提:LeakCanary 版本v2.4; Android 8.0LeakCanary相信很多開發(fā)者都用過,也是目...
    baifanger閱讀 660評論 0 0

友情鏈接更多精彩內(nèi)容