[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主流程
核心處理方法跟蹤
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中新增了對象隘谣,同時弱引用為空了增拥;