leakcanary思想全解析

[Toc]

頂層設(shè)計(jì)思想

頂層設(shè)計(jì)思路:提供一款應(yīng)用運(yùn)行時(shí)泄露檢測(cè)系統(tǒng) , 通過檢測(cè)實(shí)例生命周期不一致導(dǎo)致的泄露進(jìn)行可視化顯示

依賴底層技術(shù)

在進(jìn)行檢測(cè)系統(tǒng)開發(fā)的時(shí)候,會(huì)依賴底層技術(shù)進(jìn)行泄露的檢測(cè)依據(jù);
java的內(nèi)存泄露檢測(cè)的底層技術(shù)依賴就來自,4中類型的對(duì)象再gc時(shí)候的不同表現(xiàn),提煉出一套可信的檢測(cè)機(jī)制;

詳細(xì)的文章-張紹文leakcanary和內(nèi)存泄漏:

強(qiáng)引用:new 出來的對(duì)象屬于強(qiáng)引用類型對(duì)象,在強(qiáng)引用作用域內(nèi)操作系統(tǒng)不會(huì)回收內(nèi)存,即時(shí)拋出oom異常也不會(huì)回收對(duì)象。在超過強(qiáng)引用作用域或者將實(shí)例對(duì)象置為null,就可以被垃圾回收掉;(那么activity在使用之后依據(jù)什么被回收掉了?)
軟引用: softrefrence實(shí)現(xiàn)軟引用,為強(qiáng)引用的弱化版本,在回收時(shí)機(jī)上比強(qiáng)引用多一些場(chǎng)景。當(dāng)系統(tǒng)內(nèi)存充足時(shí)候不會(huì)被回收;系統(tǒng)內(nèi)存不足時(shí)候會(huì)被回收;

軟引用再實(shí)際中應(yīng)用很多,高速緩存用到軟引用;
瀏覽器的緩存策略也來自軟引用策略,網(wǎng)頁結(jié)束就回收會(huì)造成每次加載速度慢,結(jié)束不回收又會(huì)造成內(nèi)存膨脹;

弱引用: weakrefrence實(shí)現(xiàn)弱引用,弱引用比軟引用發(fā)生回收幾率更大。弱引用再gc發(fā)生時(shí)不論內(nèi)存是否吃緊都會(huì)被回收;

這也是leakcanary依賴的底層實(shí)現(xiàn)技術(shù),其他dump和文件解析溯源引用鏈都是在這基礎(chǔ)上實(shí)現(xiàn)的;

虛引用: 虛引用并不決定對(duì)象的生命周期,一個(gè)對(duì)象僅持有虛引用那么跟沒有持有引用一樣;虛引用主要用來跟蹤對(duì)象被gc垃圾回收的活動(dòng);

虛引用在垃圾回收之前就相當(dāng)與沒有引用一樣,當(dāng)被垃圾回收后,虛引用會(huì)被加入到引用隊(duì)列中。以便在對(duì)象銷毀后,我們可以做一些自己的事情

ReferenceQueue(引用隊(duì)列):
引用隊(duì)列的主要作用是,當(dāng)一個(gè)引用被垃圾回收之前,會(huì)被放入到引用隊(duì)列中。
對(duì)于軟引用和弱引用,我們希望當(dāng)一個(gè)對(duì)象被gc掉的時(shí)候通知用戶線程,進(jìn)行額外的處理時(shí),就需要使用引用隊(duì)列了。ReferenceQueue即這樣的一個(gè)對(duì)象,當(dāng)一個(gè)obj被gc掉之后,其相應(yīng)的包裝類,即ref對(duì)象會(huì)被放入queue中。我們可以從queue中獲取到相應(yīng)的對(duì)象信息,同時(shí)進(jìn)行額外的處理。比如反向操作,數(shù)據(jù)清理等。

leakcanary主流程

leakcanary主流程

核心處理方法跟蹤

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
    //開子線程進(jìn)行分析
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

  @SuppressWarnings("ReferenceEquality") 
  // Explicitly checking for named null. 核心處理類,完成弱引用的判斷-回收-再判斷-dump內(nèi)存-分析內(nèi)存
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //嘗試去掉已經(jīng)回收的內(nèi)存
    removeWeaklyReachableReferences();
    //調(diào)試模式直接返回
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //內(nèi)存已回收直接返回
    if (gone(reference)) {
      return DONE;
    }
    //再次嘗試回收內(nèi)存 , 主要是去掉耗時(shí)gc的影響;
    gcTrigger.runGc();
    //再次嘗試去掉已經(jīng)回收的內(nèi)存
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      //再次回收還存在內(nèi)存中
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      //dump內(nèi)存信息
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //分析內(nèi)存dump文件
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

以上是leakcanary核心的如何獲取到可能有內(nèi)存泄漏的核心處理方法,在可能有泄漏的位置會(huì)開始dump內(nèi)存快照,拿到快照后會(huì)到haha庫中進(jìn)行快照處理;那么繼續(xù)跟蹤haha庫的處理;

heapdumpListener.analyze(***)最終調(diào)用到:
HeapAnalyzerService intentservice中,intentservice調(diào)用HeapAnalyzer進(jìn)行dump文件的格式化操作;

HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);

AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    
開始分析,checkForLeak();

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //內(nèi)部fileinputstream文件輸入流讀文件
      HprofParser parser = new HprofParser(buffer);
      //對(duì)文件讀取內(nèi)容進(jìn)行格式化
      Snapshot snapshot = parser.parse();
      //去除重復(fù)gcroot
      deduplicateGcRoots(snapshot);

     //通過實(shí)例id查找snapshot中含有該實(shí)例的引用調(diào)用鏈
      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));
      }
    //做notification處理;
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

庫的局限

檢測(cè)確實(shí)很方便,但是不是所有的泄露都能檢測(cè)出來;
1.對(duì)于service的泄露無法檢測(cè),因?yàn)闄z測(cè)是依賴生命周期感知的 , fragment依附于activity也可以通過activity的生命周倩來感知;

2.對(duì)于作為mainactivity的活動(dòng)無法檢測(cè),因?yàn)樵揳ctivity在棧底,除非構(gòu)建mainactivity不在棧底的操作,才好對(duì)其進(jìn)行泄露檢查;

底層驗(yàn)證代碼

        ReferenceQueue<WeakReference<LeadEntry>> referenceQueue = new ReferenceQueue<WeakReference<LeadEntry>>();
        WeakReference<LeadEntry> weakReference1 = new WeakReference(new LeadEntry(),referenceQueue);

        System.out.println("weakReference1  :   " + weakReference1.get());
        System.out.println("referenceQueue.poll()   " + referenceQueue.poll());

        //一次gc 之后不管是否內(nèi)存足夠都會(huì)回收掉內(nèi)存;
        System.gc();

        System.out.println("weakReference1  :   " + weakReference1.get());
        System.out.println("referenceQueue.poll()  " + referenceQueue.poll());
        
        
        最終執(zhí)行結(jié)果:
        
        weakReference1  :   demo.java_theory.leak_demo.LeakDemo$LeadEntry@610455d6
        referenceQueue.poll()   null
        weakReference1  :   null
        referenceQueue.poll()  java.lang.ref.WeakReference@511d50c0
        
        在weakrefrencegc之前是擁有引用的,queue中沒有該對(duì)象,在gc之后queue中新增了對(duì)象,同時(shí)弱引用為空了;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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