天天看點

leakcanary探索leakcanary探索

leakcanary探索

  • leakcanary探索
    • 什麼是leakcanary
    • 粗略工作流程
      • 記憶體洩露判定實作
        • 系統回收
        • 對象引用可達性判斷
      • heapDump的分析

Android應用開發中,OutOfMemory是一個很讓人頭疼的問題,特别是疊代時間較長的項目中,問題定位較難。在gitHub上發現一個開源項目——leakcanary,可以輔助檢測并且定位記憶體洩漏。抽個時間研究一下它的實作。

什麼是leakcanary

A memory leak detection library for Android and Java.

leakcanary 包裝了簡單易用的接口,隻需簡單若幹行代碼,就可以開始工作。通過監控對象的回收情況,定位記憶體洩漏并自動執行HeapDump,自動分析hrpof檔案找出洩露的引用鍊,在通知欄自動提示使用者。

項目内置預設實作了ActivityRefWatcher,用于監控Activity,但隻能用于Android 4.0及其之上。如果想支援4.0之下的系統,需要定義BaseActivity複寫onDestroy來獲得統一監控觸發點。

理論上,leakcanary可以做到對象級别的記憶體洩露分析,同樣的需要自己實作對應的RefWatcher,在合适的時機進行觸發分析。

粗略工作流程

根據上面的流程圖,從技術實作上,個人比較關注的是兩個點:

  • 如何判斷一個對象屬于記憶體洩漏
  • 如何在hprof檔案裡面定位到問題并抓取出來

記憶體洩露判定實作

記憶體洩漏,指的是脫離了使用場景的對象,系統進行回收的時候無法成功釋放資源,導緻系列的問題。根據定義,思路大概是這樣:

系統回收

GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc();    //不確定一定能夠觸發gc
      enqueueReferences();  //sleep一定時間(100ms),來確定gc完成
      System.runFinalization();//為什麼要放到enqueueReferences()後面?
    }
           

如我們所知,Java裡面的gc擁有各種各樣的垃圾收集算法,gc的執行具有很大的不确定性。不管是用

System.gc()

還是

Runtime.getRuntime().gc()

,都是對jvm的一個建議,引發jvm的内部垃圾算法的權重,無法保證gc一定馬上執行。是以即使sleep了一定的時間等待gc,仍有極大的可能誤報。

對象引用可達性判斷

先讓我們來看一下邏輯的執行順序。

  1. 對象引用生成key并記錄
    public void watch(Object watchedReference, String referenceName) {
        ...檢查...
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);//直接記錄key
        //使用ReferenceQueue和WeakReference來記錄引用被回收
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
        ...異步執行gc...
    }
               
    注意到這裡使用了

    ReferenceQueue

    ,查閱文檔得知,弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛拟機就會把這個弱引用加入到與之關聯的引用隊列中。

    不同類型Reference的解釋

  2. 實際的gc和判斷過程
    void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
    
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        removeWeaklyReachableReferences();
        if (gone(reference) || debuggerControl.isDebuggerAttached()) {
          return;
        }
        gcTrigger.runGc();//實際執行gc
        removeWeaklyReachableReferences();
        //如果引用對應的key仍然存在,認為是洩漏,執行heapDump
        if (!gone(reference)) {
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == null) {
            // Could not dump the heap, abort.
            return;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
          heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,heapDumpDurationMs));
        }
    }
    
    //簡單的key判斷
    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
    }
    
    //凡是存在ReferenceQueue中的,都認為是被回收的,remove之
    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.
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }
               
    回顧這個過程,有一個比較明顯的問題,就是watch的時候是直接執行了

    retainedKeys.add(key)

    ,意圖在後期通過GC來利用ReferenceQueue的enqueue,進而

    remove(key)

    。而在前面的分析中,GC的不确定是很強的,是以存在誤報的可能。

heapDump的分析

待續

繼續閱讀