news 2026/8/15 18:42:42

android LeakCanary 工作原理 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
android LeakCanary 工作原理 详解

一、LeakCanary 是什么

LeakCanary是由 Square 开源的 Android 内存泄漏检测工具,专门用于在开发阶段自动识别 Java 层的内存泄漏问题。它通过监控对象生命周期 + 弱引用检测 + 堆转储分析的组合方案,帮助开发者定位内存泄漏根因。


二、核心工作原理

2.1 检测基石:WeakReference + ReferenceQueue

LeakCanary 的核心检测机制建立在 Java 的弱引用(WeakReference)引用队列(ReferenceQueue)之上:

  • WeakReference:指向被监控对象,当对象仅被弱引用持有时,GC 会回收该对象

  • ReferenceQueue:当 WeakReference 指向的对象被 GC 回收后,该 WeakReference 会自动进入关联的 ReferenceQueue

检测逻辑

  1. 为被销毁的 Activity 等对象创建WeakReference,并绑定一个ReferenceQueue

  2. 延迟 5 秒后检查该 WeakReference 是否已进入队列

  3. 若未进入,手动触发 GC 后再检查一次

  4. 若仍未进入队列,则判定该对象发生内存泄漏

2.2 完整工作流程

LeakCanary 的工作流程可分为5 个阶段

阶段说明
1. 注册监听通过生命周期回调或 Hook 技术,感知对象进入"无用"状态的时机
2. 监控泄漏为无用对象创建弱引用,延迟检查是否被回收;泄漏对象计数达到阈值才触发分析
3. Heap Dump将 Java 堆转储为.hprof文件(会短暂冻结应用)
4. 堆分析使用Shark解析.hprof,找出阻止 GC 的引用链(leak trace)
5. 泄漏分类将泄漏分为Application LeaksLibrary Leaks两类展示

三、支持的监控类型与实现方式

LeakCanary 自动监控以下6 种对象的泄漏:

监控对象监听方式关键源码文件
ActivityApplication.registerActivityLifecycleCallbacks()监听onActivityDestroyedActivityWatcher.kt
Fragment同上 +FragmentManager.registerFragmentLifecycleCallbacks()FragmentAndViewModelWatcher.kt
Fragment View在 Fragment 生命周期回调中监听 View 销毁FragmentAndViewModelWatcher.kt
ViewModel自定义 ViewModel + 反射获取ViewModelStore.mMap,在onCleared()中遍历检测ViewModelClearedWatcher.kt
ServiceHookActivityThread.mH.mCallback监听 STOP_SERVICE + 动态代理IActivityManager.serviceDoneExecuting()ServiceWatcher.kt
RootView (Dialog等)HookWindowManagerGlobal.mViews,监听onViewDetachedFromWindowRootViewWatcher.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) } }

判定逻辑

  1. 为对象创建KeyedWeakReference(带唯一 key 的弱引用),存入watchedObjects映射表

  2. postDelay5 秒后检查引用队列

  3. 若对象已被 GC,其弱引用会进入queue,从watchedObjects移除

  4. 若对象仍在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 自研的堆分析库)解析,主要工作:

  1. 定位泄漏对象:在堆快照中找到 retained 对象

  2. 计算引用链:使用 ** dominator tree** 算法找到从 GC Root 到泄漏对象的最短引用路径

  3. 生成签名:将引用链上的类名 + 字段名串联哈希,相同签名的泄漏归为一组

  4. 标记怀疑对象:从第一个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 做精准分析

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/15 18:42:28

Windows时间统计工具Tai:软件与网站的使用时长,从此一目了然

Windows时间统计工具Tai&#xff1a;软件与网站的使用时长&#xff0c;从此一目了然 【免费下载链接】Tai &#x1f47b; 在Windows上统计软件使用时长和网站浏览时长 项目地址: https://gitcode.com/GitHub_Trending/ta/Tai 周五晚上十点&#xff0c;我合上笔记本&…

作者头像 李华
网站建设 2026/8/15 18:41:57

暗黑2存档编辑器d2s-editor快速上手:5步可视化修改角色与装备

暗黑2存档编辑器d2s-editor快速上手&#xff1a;5步可视化修改角色与装备 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor 想试一个新流派&#xff0c;最怕的不是配装思路&#xff0c;而是反复刷装备、属性点加错只能重练。暗黑2…

作者头像 李华
网站建设 2026/8/15 18:35:49

揭秘《海豚艾柯》PC 版 31 年彩蛋:多种作弊码让通关不再困难!

《海豚艾柯》PC 版 31 年彩蛋大揭秘&#xff1a;多种作弊码让通关不再困难&#xff01; 2026 年 8 月 12 日&#xff0c;我们来研究《海豚艾柯》的 PC 版本。这款游戏改编自 1993 年世嘉 CD 版的《海豚艾柯》&#xff0c;而世嘉 CD 版又是 1992 年世嘉 Genesis 原版的增强版。…

作者头像 李华
网站建设 2026/8/15 18:33:57

steampy核心功能解析:轻松管理Steam库存与市场交易

steampy核心功能解析&#xff1a;轻松管理Steam库存与市场交易 【免费下载链接】steampy A Steam trading library for python 3 项目地址: https://gitcode.com/gh_mirrors/st/steampy steampy是一款专为Python开发者设计的Steam交易库&#xff0c;它提供了简洁易用的接…

作者头像 李华
网站建设 2026/8/15 18:32:10

1Panel文件管理实战:一条任务线带你玩转Web端服务器文件操作

1Panel文件管理实战&#xff1a;一条任务线带你玩转Web端服务器文件操作 【免费下载链接】1Panel &#x1f525; 1Panel is a modern, open-source Linux server management panel and a lightweight AI management platform. 项目地址: https://gitcode.com/GitHub_Trending…

作者头像 李华