一、LeakCanary 是什么
LeakCanary 是由 Square 开源的 Android 内存泄漏检测工具,专门用于在开发阶段自动识别 Java 层的内存泄漏问题。它通过监控对象生命周期 + 弱引用检测 + 堆转储分析的组合方案,帮助开发者定位内存泄漏根因。
二、核心工作原理
2.1 检测基石:WeakReference + ReferenceQueue
LeakCanary 的核心检测机制建立在 Java 的弱引用(WeakReference) 和**引用队列(ReferenceQueue)**之上:
-
WeakReference:指向被监控对象,当对象仅被弱引用持有时,GC 会回收该对象
-
ReferenceQueue:当 WeakReference 指向的对象被 GC 回收后,该 WeakReference 会自动进入关联的 ReferenceQueue
检测逻辑:
-
为被销毁的 Activity 等对象创建
WeakReference,并绑定一个ReferenceQueue -
延迟 5 秒后检查该 WeakReference 是否已进入队列
-
若未进入,手动触发 GC 后再检查一次
-
若仍未进入队列,则判定该对象发生内存泄漏
2.2 完整工作流程
LeakCanary 的工作流程可分为 5 个阶段:
| 阶段 | 说明 |
|---|---|
| 1. 注册监听 | 通过生命周期回调或 Hook 技术,感知对象进入"无用"状态的时机 |
| 2. 监控泄漏 | 为无用对象创建弱引用,延迟检查是否被回收;泄漏对象计数达到阈值才触发分析 |
| 3. Heap Dump | 将 Java 堆转储为 .hprof 文件(会短暂冻结应用) |
| 4. 堆分析 | 使用 Shark 解析 .hprof,找出阻止 GC 的引用链(leak trace) |
| 5. 泄漏分类 | 将泄漏分为 Application Leaks 和 Library Leaks 两类展示 |
三、支持的监控类型与实现方式
LeakCanary 自动监控以下 6 种对象的泄漏:
| 监控对象 | 监听方式 | 关键源码文件 |
|---|---|---|
| Activity | Application.registerActivityLifecycleCallbacks() 监听 onActivityDestroyed |
ActivityWatcher.kt |
| Fragment | 同上 + FragmentManager.registerFragmentLifecycleCallbacks() |
FragmentAndViewModelWatcher.kt |
| Fragment View | 在 Fragment 生命周期回调中监听 View 销毁 | FragmentAndViewModelWatcher.kt |
| ViewModel | 自定义 ViewModel + 反射获取 ViewModelStore.mMap,在 onCleared() 中遍历检测 |
ViewModelClearedWatcher.kt |
| Service | Hook ActivityThread.mH.mCallback 监听 STOP_SERVICE + 动态代理 IActivityManager.serviceDoneExecuting() |
ServiceWatcher.kt |
| RootView (Dialog等) | Hook WindowManagerGlobal.mViews,监听 onViewDetachedFromWindow |
RootViewWatcher.kt |
关键源码示例
Activity 监控:
kotlin
private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity,
"${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
ViewModel 监控(通过反射 Hook):
kotlin
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {
private val viewModelMap: Map<String, ViewModel>? = try {
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) { null }
override fun onCleared() {
viewModelMap?.values?.forEach { viewModel ->
reachabilityWatcher.expectWeaklyReachable(viewModel, "...")
}
}
}
四、核心源码分析
4.1 ObjectWatcher ------ 泄漏判定中心
ObjectWatcher 是 LeakCanary 的核心类,负责判定对象是否泄漏:
kotlin
class ObjectWatcher(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
private val isEnabled: () -> Boolean
) {
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
fun watch(watchedObject: Any, description: String) {
val key = UUID.randomUUID().toString()
// 创建带 key 的弱引用,绑定引用队列
val weakReference = KeyedWeakReference(watchedObject, key, description, queue)
watchedObjects[key] = weakReference
// 延迟 5 秒后检查
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
private fun moveToRetained(key: String) {
removeWeaklyReachableObjects() // 清理已回收的对象
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 对象未被回收,触发泄漏通知
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
var ref: Reference<*>?
do {
ref = queue.poll()
if (ref != null) {
val key = (ref as KeyedWeakReference).key
watchedObjects.remove(key)
}
} while (ref != null)
}
}
判定逻辑:
-
为对象创建
KeyedWeakReference(带唯一 key 的弱引用),存入watchedObjects映射表 -
postDelay 5 秒后检查引用队列
-
若对象已被 GC,其弱引用会进入
queue,从watchedObjects移除 -
若对象仍在
watchedObjects中,说明未被回收,标记为泄漏
4.2 HeapDumpTrigger ------ 触发堆转储的"守门员"
LeakCanary 不会每次发现泄漏都立即 Dump ,而是通过 HeapDumpTrigger 进行多层拦截:
kotlin
private fun checkRetainedObjects() {
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc() // 主动触发 GC
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 拦截 1:泄漏对象数未达阈值
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
showRetainedCountNotification("App visible, waiting...")
scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
return
}
}
// 拦截 2:距离上次 HeapDump 未超过 60s
val elapsedSinceLastDump = SystemClock.uptimeMillis() - lastHeapDumpUptimeMillis
if (elapsedSinceLastDump < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDump)
return
}
// 通过拦截,执行 Heap Dump
dumpHeap(...)
}
阈值策略:
-
App 前台可见 :阈值为 5 个泄漏对象
-
App 后台不可见 :阈值为 1 个泄漏对象
4.3 GcTrigger ------ 主动触发 GC
kotlin
object Default : GcTrigger {
override fun runGc() {
Runtime.getRuntime().gc() // 比 System.gc() 更可能触发 GC
Thread.sleep(100) // 等待 GC 完成
System.runFinalization() // 执行 Finalizer
}
}
4.4 Shark ------ 堆分析引擎
Dump 生成的 .hprof 文件由 Shark(LeakCanary 自研的堆分析库)解析,主要工作:
-
定位泄漏对象:在堆快照中找到 retained 对象
-
计算引用链:使用 ** dominator tree** 算法找到从 GC Root 到泄漏对象的最短引用路径
-
生成签名:将引用链上的类名 + 字段名串联哈希,相同签名的泄漏归为一组
-
标记怀疑对象 :从第一个
LEAKING节点到第一个NOT_LEAKING节点之间的引用标记为~~~怀疑对象
五、泄漏报告解读
LeakCanary 的分析结果分为两类:
-
Application Leaks:应用自身代码导致的泄漏,需要开发者修复
-
Library Leaks:第三方库已知的泄漏,LeakCanary 内置了常见库(如 Android Framework、Support Library)的已知泄漏规则
报告示例
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
├─ com.example.LeakingSingleton class
│ Leaking: NO (a class is never leaking)
│ ↓ static LeakingSingleton.leakedViews
│ ~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].elementData[0]
│ ~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
...
六、整体架构图
┌─────────────────────────────────────────────────────────────┐
│ App 生命周期层 │
│ ActivityWatcher │ FragmentWatcher │ ServiceWatcher │ ... │
└────────────────────┬────────────────────────────────────┘
│ expectWeaklyReachable(object)
▼
┌─────────────────────────────────────────────────────────────┐
│ ObjectWatcher │
│ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
│ │KeyedWeakRef │───▶│ ReferenceQueue│◀──│ GC │ │
│ │ (watched) │ │ (poll检查) │ │ │ │
│ └─────────────┘ └─────────────┘ └────────┘ │
│ │ 5秒后未回收 │
│ ▼ │
│ onObjectRetained() ────────────────────────────────┘
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ HeapDumpTrigger │
│ 阈值检查(前台5/后台1)│ 60s间隔检查 │ 主动GC确认 │
└────────────────────┬────────────────────────────────────┘
│ 通过检查
▼
┌─────────────────────────────────────────────────────────────┐
│ Heap Dump (.hprof) │
│ Debug.dumpHprofData(heapDumpFile) │
└────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Shark 分析引擎 │
│ 解析堆文件 → 找最短引用链 → 生成签名 → 分类展示 │
└─────────────────────────────────────────────────────────────┘
七、总结
| 要点 | 说明 |
|---|---|
| 检测核心 | WeakReference + ReferenceQueue |
| 触发时机 | Activity/Fragment/ViewModel 等销毁后延迟 5 秒检查 |
| 防误报机制 | 手动触发 GC + 双重确认 + 阈值拦截 |
| 堆分析 | 使用 Shark 自研库解析 .hprof,计算 dominator tree |
| 性能保护 | 前台阈值 5、后台阈值 1、60s 内不重复 Dump |
| 扩展性 | 支持自定义 ObjectWatcher.watch() 检测任意对象 |
LeakCanary 的设计精髓在于:用弱引用做轻量筛查,用阈值策略控制 Dump 频率,用 Shark 做精准分析。