11、内存泄漏检测:LeakCanary原理,MAT工具使用,hprof文件分析

内存泄漏,说白了就是本该被回收的对象,因为被不该持有的引用拽着,死活走不了。我见过不少项目,线上崩溃率居高不下,查到最后全是内存泄漏惹的祸。今天咱们就聊聊怎么把这些「钉子户」揪出来。

11.1 内存泄漏的本质

先想一个问题:Java有GC,为什么还会泄漏?

GC回收对象的前提是——没有从GC Roots出发的强引用链。如果某个Activity已经finish了,但它的实例还被某个静态变量或者匿名内部类持有,那GC就动不了它。这就是泄漏。

核心判断标准:生命周期本该结束的对象,却仍然存活在堆中。

我在项目中遇到过最典型的场景:Fragment里注册了EventBus,但忘记在onDestroy里unregister。结果Fragment被销毁了,EventBus还持有它的引用,整个Fragment树都泄漏了。嗯,这种问题排查起来特别隐蔽。

11.2 LeakCanary原理与实战

LeakCanary是Square公司开源的自动内存泄漏检测工具。它的工作流程其实不复杂,我拆开来讲。

11.2.1 工作原理

  1. 监控对象生命周期:LeakCanary会监听Activity、Fragment的onDestroy事件。
  2. 触发GC:对象销毁后,它主动调用System.gc(),尝试回收。
  3. 检查引用:如果对象还在,说明有泄漏。它用WeakReference配合ReferenceQueue来判断。
  4. Dump HPROF:确认泄漏后,导出当前堆的快照文件(.hprof)。
  5. 分析引用链:自动解析hprof,找到从GC Roots到泄漏对象的强引用路径。

我个人习惯:在debug版本集成LeakCanary,release版本一定要移除。它本身会占用一些内存和CPU,线上环境不建议开启。

11.2.2 集成与使用

集成方式很简单,在build.gradle里加一行依赖就行:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'

启动App后,如果有泄漏,通知栏会弹出提示。点进去就能看到完整的引用链。我曾经靠这个工具,半小时定位了一个困扰团队两天的Fragment泄漏问题。

注意:LeakCanary 2.x版本不再需要手动初始化。它通过ContentProvider自动注册。如果你用的是1.x版本,记得在Application里手动调用LeakCanary.install()。

11.3 HPROF文件分析

hprof文件是Java堆的二进制快照。它记录了所有对象、数组、类信息以及它们之间的引用关系。说白了,就是内存的「全家福」。

11.3.1 如何获取hprof

除了LeakCanary自动生成,你也可以手动导出:

  • Android Studio:Profiler -> Memory -> Dump Java Heap
  • 命令行adb shell am dumpheap <进程ID> /data/local/tmp/dump.hprof
  • 代码中Debug.dumpHprofData(path)

导出后,Android的hprof格式和标准Java略有不同。需要用hprof-conv工具转换一下,MAT才能识别。

11.4 MAT工具使用指南

MAT(Memory Analyzer Tool)是Eclipse基金会出品的堆分析神器。虽然界面老了一点,但功能非常强大。

11.4.1 核心功能

功能 说明
Histogram 列出所有类的实例数量和占用内存大小
Dominator Tree 按对象支配关系展示,快速定位大对象
Path to GC Roots 查看对象到GC Roots的引用链
Top Consumers 按包名/类名分组,展示内存占用最多的模块

11.4.2 实战步骤

我一般按这个流程来排查泄漏:

  1. 打开hprof:File -> Open Heap Dump。如果提示有未关闭的线程,直接点Yes。
  2. 查看Histogram:输入可疑类名(比如你的Activity),看实例数量是否异常。
  3. 右键 -> Merge Shortest Paths to GC Roots:排除软引用、弱引用、虚引用。
  4. 分析引用链:看看到底是谁持有了这个对象。

避坑指南:我曾经遇到一个情况,Histogram里Activity实例只有1个,但内存还是涨。后来发现是Bitmap对象泄漏了,每个Bitmap都持有Activity的Context引用。所以别只盯着Activity看,也要关注它关联的大对象。

11.5 知识体系总览

下面这张图总结了内存泄漏检测的核心流程和工具关系:

内存泄漏检测知识体系 LeakCanary 手动Dump 代码触发 HPROF 堆快照文件 MAT (Memory Analyzer) Android Studio LeakCanary 自动分析 Histogram Dominator Tree GC Roots路径 Top Consumers

11.6 常见泄漏模式与排查技巧

根据我的经验,Android里最常见的泄漏模式就这几种:

  • 静态变量持有Activity:比如单例里存了Context。解决方案是用Application Context。
  • 内部类/匿名类:Handler、Runnable、AsyncTask隐式持有外部类引用。我建议用静态内部类+弱引用。
  • 注册未反注册:EventBus、BroadcastReceiver、观察者模式。一定要成对出现。
  • 资源未关闭:Cursor、FileStream、Bitmap。用完记得recycle或close。
  • WebView:WebView持有Activity引用,销毁时记得调用destroy()。

一个小技巧:用MAT分析时,如果Histogram里某个类的实例数量比预期多,先别急着看引用链。先看看这些实例的「Retained Heap」大小。有时候泄漏的对象本身不大,但它引用的Bitmap、数组才是内存大户。

嗯,内存泄漏检测这块,工具只是辅助。关键还是得理解引用关系和生命周期。你想想看,一个对象该不该被回收,其实就一句话:它的使命完成了没有?


公众号:蓝海资料掘金营,微信deep3321