leakcanary思想全解析

[Toc]

頂層設(shè)計思想

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

依賴底層技術(shù)

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

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

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

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

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

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

虛引用: 虛引用并不決定對象的生命周期脓诡,一個對象僅持有虛引用那么跟沒有持有引用一樣;虛引用主要用來跟蹤對象被gc垃圾回收的活動媒役;

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

ReferenceQueue(引用隊列):
引用隊列的主要作用是,當(dāng)一個引用被垃圾回收之前穿仪,會被放入到引用隊列中席爽。
對于軟引用和弱引用,我們希望當(dāng)一個對象被gc掉的時候通知用戶線程啊片,進(jìn)行額外的處理時拳昌,就需要使用引用隊列了。ReferenceQueue即這樣的一個對象钠龙,當(dāng)一個obj被gc掉之后炬藤,其相應(yīng)的包裝類,即ref對象會被放入queue中碴里。我們可以從queue中獲取到相應(yīng)的對象信息沈矿,同時進(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)存 根竿, 主要是去掉耗時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)存泄漏的核心處理方法就珠,在可能有泄漏的位置會開始dump內(nèi)存快照,拿到快照后會到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);
      //對文件讀取內(nèi)容進(jìn)行格式化
      Snapshot snapshot = parser.parse();
      //去除重復(fù)gcroot
      deduplicateGcRoots(snapshot);

     //通過實例id查找snapshot中含有該實例的引用調(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));
    }
  }

庫的局限

檢測確實很方便腰耙,但是不是所有的泄露都能檢測出來榛丢;
1.對于service的泄露無法檢測,因為檢測是依賴生命周期感知的 挺庞, fragment依附于activity也可以通過activity的生命周倩來感知晰赞;

2.對于作為mainactivity的活動無法檢測,因為該activity在棧底选侨,除非構(gòu)建mainactivity不在棧底的操作宾肺,才好對其進(jì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)存足夠都會回收掉內(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中沒有該對象,在gc之后queue中新增了對象隘谣,同時弱引用為空了增拥;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市寻歧,隨后出現(xiàn)的幾起案子禀晓,更是在濱河造成了極大的恐慌饥伊,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異馋没,居然都是意外死亡奢米,警方通過查閱死者的電腦和手機(jī)撞秋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門饰豺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痢站,你說我怎么就攤上這事磷箕。” “怎么了阵难?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵岳枷,是天一觀的道長。 經(jīng)常有香客問我,道長空繁,這世上最難降的妖魔是什么殿衰? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮盛泡,結(jié)果婚禮上闷祥,老公的妹妹穿的比我還像新娘。我一直安慰自己饭于,他們只是感情好蜀踏,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布维蒙。 她就那樣靜靜地躺著掰吕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颅痊。 梳的紋絲不亂的頭發(fā)上殖熟,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音斑响,去河邊找鬼菱属。 笑死,一個胖子當(dāng)著我的面吹牛舰罚,可吹牛的內(nèi)容都是我干的纽门。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼营罢,長吁一口氣:“原來是場噩夢啊……” “哼赏陵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饲漾,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝙搔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后考传,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吃型,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年僚楞,在試婚紗的時候發(fā)現(xiàn)自己被綠了勤晚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泉褐,死狀恐怖运翼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兴枯,我是刑警寧澤血淌,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響悠夯,放射性物質(zhì)發(fā)生泄漏癌淮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一沦补、第九天 我趴在偏房一處隱蔽的房頂上張望乳蓄。 院中可真熱鬧,春花似錦夕膀、人聲如沸虚倒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魂奥。三九已至,卻和暖如春易猫,著一層夾襖步出監(jiān)牢的瞬間耻煤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工准颓, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留哈蝇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓攘已,卻偏偏與公主長得像炮赦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子样勃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355