如果你已经阅读了LeakCanary源码分析第一讲,那么LeakCanary的基本架构应该已经掌握了。本文将详细分析RefWatcher的工作原理,当RefWatcher检查到引用路径不是弱通路的时候就会触发HeapDumper。
WeakReference和ReferenceQueue
要理解RefWatcher的工作原理,首先需要知道WeakReference。当GC线程扫描它所管辖的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否(这一点与SoftReference不同),都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
WeakReference和ReferenceQueue联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。上图中的实线a表示强引用,虚线aa表示弱引用。如果切断a,那么Object对象将会被回收。正如下面这段测试代码所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
A a = new A(); ReferenceQueue queue = new ReferenceQueue(); WeakReference aa = new WeakReference(a, queue); a = null; Runtime.getRuntime().gc(); System.runFinalization(); try { //TOOD线程睡觉(由于本博客采用百度安全防护,贴现场睡觉的代码不让通过) } catch (InterruptedException e) { e.printStackTrace(); } Reference poll = null; while ((poll = queue.poll()) != null) { System.out.println(poll.toString()); } |
当垃圾回收器回收对象的时候,aa
这个弱引用将会入队进入ReferenceQueue
,所以queue.poll()
将不会为空,除非这个对象没有被垃圾回收器清理。
执行这段代码,你将会看到一句打印。类似于:java.lang.ref.WeakReference@2352544e
RefWatcher工作原理
如果将上述代码第四行注释掉,会得到什么结果呢?答案肯定是没有任何输出,因为,如果a!=null
那么就存在强引用指向a对象,垃圾回收器自然不会回收它,也就不会将aa
这个弱引用入队。这就是RefWatcher的核心工作原理。
RefWatcher核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); removeWeaklyReachableReferences();//移除弱引用 if (gone(reference) || debuggerControl.isDebuggerAttached()) {//如果引用已经不存在了,或者处于debug调试中则返回 return; } gcTrigger.runGc();//触发GC removeWeaklyReachableReferences();//再次移除弱引用,二次确认 if (!gone(reference)) {//如果GC之后引用还是存在,那么就进行深入分析 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap();//dump内存 if (heapDumpFile == null) { // Could not dump the heap, abort. return; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key,//分析Hprof文件 reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } } |
代码已经注释了,只要你了解了WeakReference的原理,其实非常简单。这里就不详细分析了。剩下的工作就是交给HeapAnalyzer分析对象的最短强引用路径了。这将在下一篇文章中进行分析。
RefWatcher工作流程
- RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
- 然后在后台线程检查引用是否被清除,如果没有,调用GC。
- 如果引用还是未被清除,把 heap 内存 dump 到 文件系统中的一个 .hprof 文件中。
- 在另外一个进程中,HeapAnalyzerService 通过 HeapAnalyzer 使用HAHA 解析这个文件。
- 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄漏。
- HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否泄漏。如果是,建立导致泄漏的引用链。
- 引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
总结
本篇文章虽然是讲RefWatcher的工作原理,其实重点讲解了WeakReference。如果你对Java对象的引用类型还不了解,那么请一定要去学习充电了。
一起学习
向大神致敬
LeakCanary年度最优秀的开源项目之一
大神,期待您的下一篇文章