LeakCanary 原理分析
源码参考:square/leakcanary (GitHub)
一、整体结构
LeakCanary
├── 监听入口:ActivityLifecycleCallbacks、Fragment 等
├── RefWatcher - 核心,WeakReference + ReferenceQueue 检测
├── KeyedWeakReference - 带 key 的弱引用
├── HeapDumper - 生成 .hprof 内存快照
└── HeapAnalyzer - 解析 hprof,构建泄漏路径
二、核心原理:WeakReference + ReferenceQueue
2.1 Java 引用与 ReferenceQueue
当对象只被 WeakReference 引用时,GC 会回收该对象。回收后,对应的 WeakReference 会被放入关联的 ReferenceQueue。
正常回收:
对象 ──(仅被 WeakReference 引用)──→ GC 回收
│
WeakReference ──────────────────────────┘
│
└──→ 自动入队 ReferenceQueue
内存泄漏:
对象 ──(被其他强引用持有)──→ GC 不回收
│
WeakReference ──→ 不入队,retainedKeys 仍包含其 key
2.2 KeyedWeakReference
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
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():开始监控
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) return;
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
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
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
3.3 gone():判断是否已回收
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():完整检测流程
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
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
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity, activity.getClass().getName());
}
onActivityDestroyed() 在 Activity 销毁时调用
- 此时将 Activity 传给
RefWatcher.watch(),开始监控
- Fragment、Service、ViewModel 等同理,在对应销毁时机调用
watch()
五、检测流程总览
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 | 泄漏确认后,生成快照分析引用链 |