19年年末总结一篇《LeakCanary原理从0到1》,当时还比较满意,以为自己就比较了解这个框架了,Too young, Too Simple。
周五群里一个小伙伴问:“线上做内存泄漏检测大家有什么思路吗?”。
内存泄漏检测首先想到的是 LeakCanary,可以看看能从LeakCanary上找到一些思路吗?
本文并不是从0开始解释 LeakCanary 的工作原理,所以为了阅读体验更佳,还不太了解 LeakCanary 是怎样判定对象内存泄漏的读者,可以先从《LeakCanary原理从0到1》开始阅读。
本文将从内存泄漏后 LeakCanary 的后续工作开始讲起,分析 LeakCanary 是怎么找到泄漏对象的强引用链的,分析 LeakCanary 不能直接用于线上内存检测的原因,并尝试找出线上检测内存泄漏的一些思路。
生成Dump文件
在判定有内存泄漏后,「LeakCanary」调用将系统提供的Debug.dumpHprofData(File file)
函数,改函数将生成一个虚拟机的内存快照,文件格式为 .hprof
,这个dump文件大小通常有10+M。(本文中所说的dump文件都是指内存快照的.hprof
文件)
//:RefWatcher.java
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime)
...
//内部调用Debug.dumpHprofData(File file)函数
File heapDumpFile = heapDumper.dumpHeap(file);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
.referenceKey(reference.key)
.build();
heapdumpListener.analyze(heapDump);
....
return DONE;
}
生成dump文件后,LeakCanary 将被泄露对象的 referenceKey 与 dump 文件 对象封装在 HeapDump 对象中,然后交给ServiceHeapDumpListener
处理,在ServiceHeapDumpListener
中创建 leakcanary 进程并启动服务 HeapAnalyzerService
。
解析Dump文件
dump 文件的解析工作是在HeapAnalyzerService
中完成的,主要逻辑入下:
//HeapAnalyzerService.java
//创建一个分析器
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//使用分析器分析dump文件,得到分析结果
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
//将分析结果交由listener组件处理
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
下面继续跟踪分析器逻辑,主要查看 HeapAnalyzer
的 checkForLeak
方法:
//: HeapAnalyer.java
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
boolean computeRetainedSize) {
//读取dump文件,解析文件内容并生成一个Snapshot对象
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
//消除重复的GcRoots对象
deduplicateGcRoots(snapshot);
//通过referenceKey 在Snapshot对象中找到泄漏对象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
//找到泄漏路径
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
}
在checkForLeak
方法中:
- 首先对dump文件的二进制数据进行解析,然后将文件内容信息存放在
Snapshot
对象当中,这种就可以从Snapshot
中获得JVM的内存信息。(关于dump文件格式,有兴趣的可以点击这里,同时也可去看 square 的com.squareup.haha:haha+
,LeakCanary 使用的就是这个 dump 解析库)。 - 然后在
Snapshot
中类名为KeyedWeakReference
且referenceKey
所对应的泄漏对象Instence
。 - 最后在
Snapshot
中寻找泄漏对象Instence
的泄漏强引用链
查找引用链
泄漏对象的引用链式如何被找到的呢?下面继续分析 findLeakTrace
方法:
//: HeapAnalyer.java
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef, boolean computeRetainedSize) {
//创建最短路径查找器
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
//使用查找器在snapshot中找到被泄露实例节点
Sh