LeakCanary 原理分析

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 仍在 retainedKeysgone() 为 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 泄漏确认后,生成快照分析引用链
相关推荐
没想好d1 小时前
通用管理后台组件库-13-页签组件
前端
xChive1 小时前
ECharts-大屏开发复习记录与踩坑总结
前端·javascript·echarts
南城书生1 小时前
Java HashMap 源码分析
前端
南城书生1 小时前
Java 线程池(ThreadPoolExecutor)源码分析
前端
前端Hardy1 小时前
别再靠 Code Review 纠格式了!一套自动化前端工程化方案,让 Vue 项目提交即合规
前端·程序员·代码规范
前端Hardy1 小时前
用 uni-app x 重构我们的 App:一套代码跑通 iOS、Android、鸿蒙!人力成本直降 60%
前端·ios·uni-app
林恒smileZAZ1 小时前
告别满屏 v-if:用一个自定义指令搞定 Vue 前端权限控制
前端·javascript·vue.js
南城书生1 小时前
Android View 绘制流程
前端
南城书生1 小时前
# Android 常见内存泄漏
前端