LeakCanary 原理分析
源码参考:square/leakcanary (GitHub)
一、整体结构
php
复制代码
LeakCanary
├── 监听入口:ActivityLifecycleCallbacks、Fragment 等
├── RefWatcher - 核心,WeakReference + ReferenceQueue 检测
├── KeyedWeakReference - 带 key 的弱引用
├── HeapDumper - 生成 .hprof 内存快照
└── HeapAnalyzer - 解析 hprof,构建泄漏路径
二、核心原理:WeakReference + ReferenceQueue
2.1 Java 引用与 ReferenceQueue
当对象只被 WeakReference 引用时,GC 会回收该对象。回收后,对应的 WeakReference 会被放入关联的 ReferenceQueue。
php
复制代码
正常回收:
对象 ──(仅被 WeakReference 引用)──→ GC 回收
│
WeakReference ──────────────────────────┘
│
└──→ 自动入队 ReferenceQueue
内存泄漏:
对象 ──(被其他强引用持有)──→ GC 不回收
│
WeakReference ──→ 不入队,retainedKeys 仍包含其 key
2.2 KeyedWeakReference
java
复制代码
// KeyedWeakReference.java
final class KeyedWeakReference extends WeakReference<Object> {
public final String key; // 随机 UUID,用于标识
public final String name; // 逻辑名,如 "Activity"
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(referent, referenceQueue); // 关联引用队列
this.key = key;
this.name = name;
}
}
- referent:被监控对象(如 Activity)
- key :UUID,存入
retainedKeys,用于判断是否已回收
- referenceQueue:对象被 GC 后,该 Reference 会入队
三、RefWatcher:核心检测逻辑
3.1 watch():开始监控
java
复制代码
// RefWatcher.java
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) return;
String key = UUID.randomUUID().toString();
retainedKeys.add(key); // 1. 将 key 加入强引用 Set
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 2. 创建弱引用,关联 queue
ensureGoneAsync(watchStartNanoTime, reference);
// 3. 异步执行检测
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
| 步骤 |
逻辑 |
说明 |
| 1 |
retainedKeys.add(key) |
用强引用 Set 持有 key,与 WeakReference 分离 |
| 2 |
new KeyedWeakReference(..., queue) |
弱引用指向对象,并关联 ReferenceQueue |
| 3 |
ensureGoneAsync() |
延迟后(如 5s)在子线程执行 ensureGone() |
3.2 removeWeaklyReachableReferences():清理已回收的 key
java
复制代码
// 当对象被 GC 回收时,其 WeakReference 会入队
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key); // 从 Set 中移除,表示已回收
}
}
3.3 gone():判断是否已回收
java
复制代码
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
- 对象被 GC → WeakReference 入队 →
removeWeaklyReachableReferences() 移除 key → gone() 为 true
- 对象未被 GC(泄漏)→ WeakReference 不入队 → key 仍在
retainedKeys → gone() 为 false
3.4 ensureGone():完整检测流程
java
复制代码
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
removeWeaklyReachableReferences();
// 1. 先清理已入队的引用
if (debuggerControl.isDebuggerAttached()) {
return RETRY; // 调试器可能造成误报
}
if (gone(reference)) {
return DONE; // 2. 已回收,结束
}
gcTrigger.runGc();
// 3. 主动触发 GC
removeWeaklyReachableReferences();
if (!gone(reference)) {
// 4. 仍未回收,判定泄漏
File heapDumpFile = heapDumper.dumpHeap();
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, ...));
}
return DONE;
}
| 步骤 |
逻辑 |
说明 |
| 1 |
removeWeaklyReachableReferences() |
处理已入队的 Reference,移除对应 key |
| 2 |
gone(reference) |
若 key 已移除,说明已回收,直接返回 |
| 3 |
gcTrigger.runGc() |
主动触发 GC |
| 4 |
再次 removeWeaklyReachableReferences() + gone() |
若仍存在,判定泄漏,执行 heap dump |
四、监听入口:Activity 销毁时 watch
java
复制代码
// ActivityRefWatcher(或类似实现)
// 通过 Application.registerActivityLifecycleCallbacks 注册
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity, activity.getClass().getName());
}
onActivityDestroyed() 在 Activity 销毁时调用
- 此时将 Activity 传给
RefWatcher.watch(),开始监控
- Fragment、Service、ViewModel 等同理,在对应销毁时机调用
watch()
五、检测流程总览
scss
复制代码
Activity.onDestroy()
│
└── refWatcher.watch(activity)
│
├── retainedKeys.add(key)
├── new KeyedWeakReference(activity, key, name, queue)
└── ensureGoneAsync()
│
└── 延迟 5s 后,子线程执行 ensureGone()
│
├── removeWeaklyReachableReferences()
│ └── queue.poll(),已回收的 Reference 入队,移除对应 key
│
├── gone(reference) ?
│ ├── true ──→ 结束
│ └── false ──→ 继续
│
├── gcTrigger.runGc()
├── removeWeaklyReachableReferences()
├── gone(reference) ?
│ ├── true ──→ 结束
│ └── false ──→ 判定泄漏
│
└── heapDumper.dumpHeap()
└── heapdumpListener.analyze()
└── 解析 hprof,构建 GC Root → 泄漏对象路径
六、Heap Dump 与泄漏路径分析
| 阶段 |
职责 |
| HeapDumper |
调用 Debug.dumpHprofData() 生成 .hprof 文件 |
| HeapAnalyzer |
解析 hprof,找到 KeyedWeakReference 的 referent(泄漏对象) |
| 路径构建 |
从泄漏对象反向追溯到 GC Root,得到引用链 |
| 展示 |
通知栏展示泄漏路径,便于定位 |
七、关键点小结
| 机制 |
作用 |
| WeakReference |
不阻止 GC,对象可被回收 |
| ReferenceQueue |
对象被回收后,Reference 入队,用于判断 |
| retainedKeys |
强引用 Set 存 key,与 Reference 分离,避免误判 |
| 延迟检测 |
给 GC 时间,避免误报 |
| 主动 GC |
确保已触发回收再判断 |
| Heap Dump |
泄漏确认后,生成快照分析引用链 |