[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主流程

核心處理方法跟蹤
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í)弱引用為空了;