LeakCanary源碼解析

LeakCanary

一個內(nèi)存泄漏檢測庫,適用于Android和Java環(huán)境

“A small leak will sink a great ship.” - Benjamin Franklin

小漏不補沉大船

使用效果如下圖:

demo效果截圖.png
demo效果截圖.png

接入步驟如下:

在你項目對應(yīng) build.gradle添加:

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

在你項目對應(yīng) Application 添加初始化:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

接入完成! LeakCanary在發(fā)現(xiàn)內(nèi)存泄漏的時候會彈出通直欄以及創(chuàng)建一個快捷方式,每個泄漏對應(yīng)一個快捷方式,快捷方式圖標(biāo)圖標(biāo)如下:有問題請點擊!

內(nèi)存泄露圖標(biāo).png
內(nèi)存泄露圖標(biāo).png

LeakCanray實現(xiàn)原理

基礎(chǔ)概念

ActivityLifecycleCallbacks

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);
}

Application通過此接口提供了一套回調(diào)方法,用于讓開發(fā)者對Activity的生命周期事件進行集中處理,可用于替換runningProcess或者runningTasks,判斷 app 是否在后臺運行
Android 4.0 (API Level 14) 引入

Java對象的強、軟、弱和虛引用

強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當(dāng)內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題。

軟引用(SoftReference)
如果一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存(下文給出示例)。軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。

弱引用(WeakReference)
弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。不過,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。

虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之 關(guān)聯(lián)的引用隊列中。

工作機制

LeakCannary檢測泄露主要分三步:

1.關(guān)聯(lián)需要監(jiān)聽的對象 leakcanary-android

2.檢測監(jiān)聽對象是否存在泄露嫌疑,如果存在嫌疑,就dump出內(nèi)存快照到*.hprof文件 leakcanary-watcher

3.通過分析*.hprof文件確認是否真正泄露,泄露了就保存并展示結(jié)果 leakcanary-analyzer

關(guān)聯(lián)監(jiān)聽對象關(guān)鍵類ActivityRefWatcher

默認情況下,我們監(jiān)聽的是項目里的Activity.
利用ActivityLifecycleCallbacks的監(jiān)聽所有Activity生命周期,在Activity被銷毀時進行泄露檢測

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        @Override public void onActivityStarted(Activity activity) {
        }

        @Override public void onActivityResumed(Activity activity) {
        }

        @Override public void onActivityPaused(Activity activity) {
        }

        @Override public void onActivityStopped(Activity activity) {
        }

        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        @Override public void onActivityDestroyed(Activity activity) {
            // 最關(guān)鍵的,activity被銷毀時,進行檢查
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

我們也可以使用RefWatcher關(guān)聯(lián)其他任何我們想要監(jiān)聽的對象:

// 使用 RefWatcher 監(jiān)控 Fragment:
public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this); // watch傳入的是Object對象
  }
}

確認是否進行分析,關(guān)鍵類RefWatcher

我們知道弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
LeakCanary利用弱引用這個特性,將要監(jiān)聽的對象和ReferenceQueue隊列聯(lián)合使用,如果對象被垃圾系統(tǒng)回收,就會放到隊列里,就可以利用這個隊列找出沒有被回收的對象,沒有被回收的則懷疑可能發(fā)生了內(nèi)存泄露,如下面的removeWeaklyReachableReferences()

// 這個方法執(zhí)行完,retainedKeys列表里就剩下沒有被系統(tǒng)回收的
private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    // queue里裝的都算被垃圾系統(tǒng)回收的
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key); // 刪除已經(jīng)被系統(tǒng)回收
    }
  }

下面看完整的流程,主要在ensureGone()

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    // 1.刪除已經(jīng)被系統(tǒng)回收的對象
    removeWeaklyReachableReferences(); 

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 2.判斷沒被回收的列表retainedKeys是否有reference對象
    if (gone(reference)) {
      return DONE;
    }
    // 3.調(diào)用gc
    gcTrigger.runGc();
    // 4.再次刪除已經(jīng)被系統(tǒng)回收的對象
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      // 5.retainedKeys列表里存在reference對象,存在泄漏嫌疑
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // 6.dump出內(nèi)存快照到*.hprof文件
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      // 7.觸發(fā)對.hprof文件進行分析
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

分析*.hprof文件,找到泄露對象,并展示

ServiceHeapDumpListener實現(xiàn)了HeapDump.Listener接口,當(dāng)RefWatcher發(fā)現(xiàn)可疑引用的之后,它將dump出來的*.hprof文件通過ServiceHeapDumpListener傳遞到HeapAnalyzerService

HeapAnalyzerService它主要是通過HeapAnalyzer.checkForLeak分析對象的引用,計算出到GC root的最短強引用路徑。然后將分析結(jié)果傳遞給DisplayLeakService

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    // 判斷*.hprof是否存在
    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      // 利用HAHA(基于MAT的堆棧解析庫)將之前dump出來的內(nèi)存文件解析成Snapshot對象
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse(); 
      deduplicateGcRoots(snapshot);

      // 找到泄露對象,LeakCanary通過被泄漏對象的弱引用來在Snapshot中定位它。
      // 如果一個對象被泄漏,一定也可以在內(nèi)存中找到這個對象的弱引用,再通過弱引用對象的referent就可以直接定位被泄漏對象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }
      
      // 計算出一條有效的到被泄漏對象的最短的引用
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

DisplayLeakService繼承了AbstractAnalysisResultService。它主要是用來處理分析結(jié)果,將結(jié)果寫入文件,然后在通知欄報警

// listenerClassName對應(yīng)的就是DisplayLeakService
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);

下面是以上步驟的時序圖

泄漏監(jiān)聽時序圖.jpg
泄漏監(jiān)聽時序圖.jpg
泄漏處理時序圖.jpg
泄漏處理時序圖.jpg

其他說明

LeakCanary提供了ExcludedRefs來靈活控制是否需要將一些對象排除在考慮之外,因為在Android Framework,手機廠商rom自身也存在一些內(nèi)存泄漏,對于開發(fā)者來說這些泄漏是我們無能為力的,所以在AndroidExcludedRefs中定義了很多排除考慮的類

未完待續(xù)。。。

風(fēng)格:
1、所有接口內(nèi)部都有個一個默認的實現(xiàn)
2、final類
3、沒有util,Preconditions,AndroidDebuggerControl
4、import方式import static com.squareup.leakcanary.AnalysisResult.leakDetected

參考文章

http://vjson.com/wordpress/leakcanary%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E7%AC%AC%E4%B8%80%E8%AE%B2.html

http://www.itdecent.cn/p/5032c52c6b0a

http://coolpers.github.io/leakcanary%7Cmat/2015/06/04/LeakCanary-Brief.html

http://www.itdecent.cn/p/481775d198f0

https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

最后編輯于
?著作權(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)容

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