如果你已经阅读了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对象的引用类型还不了解,那么请一定要去学习充电了。