第1章 LeakCanary工作原理:Reference链、GC Root、弱引用、内存快照

1.1 内存泄漏的本质

Java/Kotlin 虚拟机通过 GC Root 可达性分析判定对象是否可回收。如果一个不再使用的对象仍然被 GC Root 通过强引用链间接引用,GC 就无法回收它,造成内存泄漏。LeakCanary 的核心就是反向追踪这条泄漏链条。

1.2 四大关键概念

概念定义在 LeakCanary 中的作用
GC Root 堆外引用起点(栈帧、静态字段、JNI 全局引用等) LeakCanary 从泄漏对象出发,反向追踪到最近的 GC Root
强引用链 从 GC Root 到对象的所有强引用路径 确定对象为何未被回收;LeakCanary 报告这条最短路径
弱引用 不影响 GC 回收的引用类型 LeakCanary 在对象销毁时注册弱引用,通过其入队情况判断是否泄漏
内存快照 堆转储文件(.hprof),保存所有对象及其引用关系 确认泄漏后自动 dump,用于分析泄漏路径

1.3 Reference 链与 GC Root 可达性

GC 从 GC Root 集合出发,遍历所有强引用。无法遍历到的对象即为不可达,可回收。如果某个预期应被销毁的 Activity 仍然可达,则泄漏存在。LeakCanary 捕获泄漏后,使用 haha 库解析 .hprof 文件,计算出从 GC Root 到泄漏对象的最短强引用路径,并去掉弱引用、软引用等干扰。

1.4 弱引用的关键作用

LeakCanary 在 Activity#onDestroy() 后创建一个指向该 Activity 的 WeakReference,并关联一个 ReferenceQueue。当 GC 回收 Activity 时,弱引用会被加入队列。LeakCanary 定期检查队列:如果弱引用未被入队,说明 Activity 仍然被强引用持有,触发 dump。

// LeakCanary 内部简化逻辑
val refQueue = ReferenceQueue<Any>()
val weakRef = WeakReference(activity, refQueue)

// 在后台线程检查
if (refQueue.poll() == null) {
    // 弱引用未被回收 → 触发内存快照
    heapDumper.dump()
}

💡 关键点:弱引用本身不阻止 GC,因此 LeakCanary 的检测对性能影响极小。但若对象被强引用持有,弱引用会一直有效,从而暴露泄漏。

1.5 内存快照与泄漏链分析

当 LeakCanary 判定对象泄漏后,会调用 Debug.dumpHprofData() 生成 .hprof 文件。随后解析该文件,构建完整的对象关系图,找到从 GC Root 到泄漏对象的最短路径。最终呈现的泄漏链类似:

GC Root (Thread)
  └─ com.example.MyActivity (泄露对象)
        └─ mStaticField (静态字段)
              └─ leakedActivity

⚠️ 性能影响:内存快照操作会暂停应用所有线程(stop-the-world),导致短暂卡顿。LeakCanary 默认仅在应用处于后台或空闲时触发 dump,以降低用户感知。

1.6 整体工作流程

LeakCanary 的检测周期可归纳为以下步骤:

  1. 监控启动:注册 Activity 生命周期监听。
  2. 创建弱引用:Activity#onDestroy() 后创建 WeakReference + ReferenceQueue。
  3. 延迟检查:等待 5 秒(可配置)后检查弱引用是否入队。
  4. 触发 dump:弱引用未回收 → 调用 Debug.dumpHprofData()。
  5. 解析快照:遍历 .hprof,找到泄漏对象,计算最短 GC Root 路径。
  6. 展示报告:通知栏显示泄漏链,点击可查看详情。
LeakCanary 工作原理流程图 GC Roots 栈帧 / 静态字段 / JNI 强引用链 可达路径 泄漏对象 例如已销毁 Activity 弱引用 (WeakReference) + ReferenceQueue 内存快照 .hprof dump 泄漏分析报告 GC Root 最短路径 未回收 弱引用入队列检查 未被回收 → 触发 dump

🎯 总结:LeakCanary 利用弱引用 + ReferenceQueue 巧妙避开持续监测的消耗,仅在确认泄漏后才生成内存快照并解析引用链。理解 Reference 链、GC Root 和弱引用之间的关系,是正确使用和定制 LeakCanary 的基础。