4. 内存分析工具(二):MAT(安装、使用与泄漏分析

聊完抓取堆转储文件,接下来就是真正的重头戏——怎么分析这些成百上千兆的 dump 文件。我个人习惯用 MAT,这东西虽然叫 Memory Analyzer Tool,但你把它当“内存侦探事务所”来用就行。它能帮你从一堆对象里揪出那个“该回收却死活不回收”的嫌疑人。

核心逻辑:MAT 把堆快照解析成对象图,然后通过支配树、GC 根路径、OQL 查询等机制,替你找到泄漏点。说白了就是“谁占着茅坑不拉屎”一目了然。

MAT 内存泄漏分析流程 ① 获取 hprof 文件 ② 导入 MAT ③ 计算支配树 ④ 查找 GC Root 路径 ⑤ 识别泄漏原因 💡 关键是找到最短 GC Root 路径 结合代码确认对象是否应当存活

4.1 安装与配置

MAT 是 Eclipse 基金会的开源项目,最新版可以从官网下载。嗯,这里要注意:MAT 本身需要 JDK 11 及以上才能跑,而且你最好给它分配至少 4GB 的堆内存——不然你分析一个 2GB 的 hprof 文件,它自己就先 OOM 了。

修改启动配:打开 MemoryAnalyzer.ini,找到 -Xmx 参数,改成 -Xmx4096m。我在项目中遇到过因为没改这个参数,导致 MAT 直接闪退,白白浪费半天时间。

4.2 导入堆转储文件

把从设备 dump 出来的 .hprof 文件拖进 MAT 窗口就行。如果你用 Android Studio 的 Profile → Dump Java Heap 得到的文件,可以直接用;如果是用 adb shell am dumpheap 抓的,注意要先转换成标准格式:

# 使用 hprof-conv 转换
hprof-conv raw_dump.hprof standard_dump.hprof

tip:转换工具在 Android SDK 的 platform-tools/ 目录下。我第一次转换时忘了指定输出文件名,差点以为工具坏了。

4.3 基础分析操作

导入成功后,MAT 会弹出“泄漏嫌疑报告”(Leak Suspects Report)。别急着点“Finish”,先看看基本信息:

  • Total size:堆总大小,跟你预期是不是一致。
  • Class loader / unreachable objects:通常不重要,可以关掉。

然后我建议你点一下 Keep Unreachable Objects,虽然会增大分析量,但有时候泄漏的对象恰好是从不可达状态变成可达的,排查更彻底。

核心操作就两个视图:

  1. Histogram:按类的实例数排序,快速找出哪个类创建了海量对象。
  2. Dominator Tree:显示对象间的支配关系,最大的“支配者”往往是泄漏的根。

你想想看,如果某个 Activity 实例数远远超出你的预期(比如超过预设的 1 个),那大概率就是泄漏了。为什么?因为正常情况下 Activity 销毁后会被 GC 回收掉。

4.4 泄漏定位实战

我拿一个真实的案例说明。当时公司的新闻列表页,用户反复进入退出,内存暴涨。我用 Histogram 一看,NewsListActivity 的实例数居然有 17 个。

步骤操作结果
1右键 NewsListActivity → Merge Shortest Paths to GC Roots → exclude all phantom/weak/soft references只显示强引用路径
2观察路径中的对象发现被一个静态的 Handler 持有
3检查该 Handler 的 target是匿名内部类,隐式持有外部 Activity 的引用
4添加 Handler.removeCallbacks() 即可修复

还有一种常见场景:Bitmap 泄漏。用 Histogram 看 Bitmap 对象的总 bytes 占用,如果反复操作后 bytes 没降,多半是某个 Context 或 View 持有了 Bitmap 没释放。

4.5 OQL 查询

MAT 自带的 OQL(对象查询语言)很像 SQL,适合按特定条件过滤。比如我想找所有大于 1MB 的字节数组:

SELECT * FROM byte[] b WHERE b.length > 1024*1024

用 OQL 的好处是精准。比如你可以查 SELECT toString(s) FROM java.lang.String s WHERE toString(s) LIKE "%leak%" 来快速找到可疑字符串。

⚠️ 注意:OQL 查询耗内存,不要一次返回几十万行,尽量用 LIMIT 100 截断。

4.6 避坑指南

  • 我曾经 因为直接打开生产环境 3GB 的 hprof,MAT 卡死半小时。之后我学乖了:先减小 dump 范围,或者只分析特定类。
  • 不要忽略 WeakReference:有些泄漏是 WeakReference 被意外清除后又重新被强引用持有,需要结合代码逻辑判断。
  • 对比两次 dump:如果你有操作前后的堆快照,用 MAT 的 Compare Tables 功能,能非常直观地看到哪些对象增加了。
  • 别只信 Leak Suspects Report:那个报告是启发式的,有时会误报。我建议把它当线索,最终还是要靠 GC Root 路径确认。

MAT 的学习曲线其实不陡,关键是亲自动手走一遍流程。下次你再遇到内存抖动或 OOM,不用慌,打开 MAT,按上面五步走,90% 的泄漏都能揪出来。