本文是 MAT 工具使用系列的第二篇,这个系列共三篇,详细介绍了如何使用 MAT 来分析内存问题,既可以是 Java 应用的内存问题,也可以是 Android 应用的内存问题:
技术复核(2026-10-13):保留 2015 年的操作截图,概念与菜单说明对照 MAT 1.17.0 文档。旧 Android Studio 的右键转换和 GC 按钮不代表当前界面;下文修正引用方向、Unreachable 和泄漏判定,并补上缺失的分析步骤。
抓取流程见本系列入门篇:先在 Android Studio Profiler 以 debuggable / complete data 模式获取 heap dump,再按需要导出给 MAT。自动化测试可使用 LeakCanary,但普通 leakcanary-android 依赖应放在 debug 构建;官方 2.14 文档里的 release 分析方案仍标为实验性质,不能把“接入 LeakCanary”直接当成通用线上采集方案。UI 测试集成 / release 方案
Java的内存泄露的特点
- Java 内存泄漏常见的特征是:可达,无用。对象还被保留着,业务上却已经用不到了。
- 需要把对象的预期生命周期与实际保留它的引用路径对照,不能只看对象大不大、数量多不多。
- 可以复用却反复创建对象,会增加分配和 GC 开销;如果旧对象能正常回收,这是分配浪费,不一定是泄漏。OOM 也可能来自一次过大的正常分配。
MAT使用技巧进阶
使用Android Studio Dump内存文件
下面是早期 Android Studio 的抓取界面,保留作为历史记录:

当前流程以 Android Studio heap dump 文档为准:选择 **Analyze Memory Usage (Heap Dump)**,检查实例及引用,再从 Past Recordings 导出 .hprof。需要在程序中的特定位置抓取时,也可调用 Debug.dumpHprofData();抓取会带来暂停和额外开销,不适合当成无成本的常驻采样。
Android 格式的 HPROF 交给不支持该格式的分析器前,按需用 hprof-conv 转换,输入和输出保持不同文件名;不要把旧截图的“右键转换”当作每个 Studio 版本都有的菜单。
手动 GC 不是必做前提,也不是结果保证。 Runtime.gc() 是尽力回收的请求,不能承诺随后快照完全没有 Unreachable 对象。查长期保留对象时,可在相同业务状态下比较 GC 后快照;查短命对象和分配浪费时,提前 GC 反而可能删掉需要的线索。下面是旧版按钮位置:

Unreachable对象
在某次解析所建立的对象图中,没有从普通 GC Roots 到达的路径的对象,会被视为不可达。常见情况是已无用途但尚未回收的对象;dump 格式、解析选项和对象丢弃设置也会影响结果,不能反推为“之前肯定没发生 GC”。


MAT 默认解析会从主对象图移除这些对象,保留类名、数量和 shallow size 的汇总,因此这个 Unreachable Objects Histogram 背后没有可逐个遍历的完整引用图。官方说明
旧图展示了 Calculate Retained Size 的菜单:Minimum 是较快的下界估计,Precise 用于精确计算对象集合的保留大小,两者不应混用。


图中的 Retained Heap 列为 0,是这张旧汇总表的显示结果,不表示这些对象没有占用内存,也不能推广成“所有不可达对象的 retained size 永远为 0”。在保留了对象图的解析方式下,分析条件已经不同。
如果要继续看这些对象的引用关系,可按官方步骤重新解析:
- 关闭快照。
- 在 Heap Dump History 中对该快照执行 Delete Index Files,只移除可重建索引,保留原 HPROF。
- 启用 Preferences → Memory Analyzer → Keep unreachable objects。
- 重新打开文件,等待重新解析。
- 运行 Java Basics → GC Roots,选中 Unreachable Objects,执行 Show Retained Set。
此时 MAT 用特殊的不可达根保留对象供分析;这个根是分析器构造的,不是应用真实的强引用持有者。
Histogram
Histogram 通常按类汇总实例数和 shallow heap,适合先看哪些类占用多、数量是否符合预期。
- 按对象数或 Shallow Heap 等实际存在的列排序;某个视图里的 Percentage 列不是所有表都有。
- Group by class loader、package 等分组便于缩小范围,Dominator Tree 也提供分组方式。
- 对同一流程重复进入、退出,在可比较的状态下抓取快照。增长可能来自缓存预热、延迟任务或泄漏,需要继续查生命周期与保留路径。
- Histogram 看类和数量,Dominator Tree 看支配关系。 支配树的父子边不必是实际字段引用;要查“谁引用了谁”,还得看 incoming/outgoing references。

找到了持续增长的类后,选具体实例,检查它是否本应释放,再查是谁仍在保留它。只凭数量增长,不能断言这个类泄漏。

Thread信息
MAT 展示的是快照里可用的线程信息;能否看到调用栈和局部变量取决于 dump 格式与解析设置。Thread Overview 说明

从旧图中可以继续调查两类线索:
保留较多对象的线程。查看它的任务、局部引用和上下文是否仍有用途。这里的 retained heap 不能当作该线程的 native 栈总大小。

数量异常的线程。结合线程名和代码确认线程池规模、任务是否结束;线程多本身不是泄漏的充分证据。

帮助信息
在结果项上右键可以打开分析选项:

通过 Search Queries 或 Query Browser 搜索名称,查看命令的说明和参数,不必记住菜单里的固定位置。

这些是 MAT 的分析查询,不应统称为“配置好的 SQL”。MAT 另有类似 SQL 的 OQL 对象查询语言,查询对象是堆中的实例、类和字段。
常用选项的方向见 List Objects 说明。下面几个最常用:
- List Objects → with incoming references:谁引用了当前对象。
- List Objects → with outgoing references:当前对象引用了谁。
- Path to GC Roots → exclude all phantom/weak/soft etc. references:按预设排除非强 referent 字段,寻找保留当前实例的根路径。路径存在说明可达,还要证明对象在业务上已无用途,才能判断泄漏;也不能据此直接推断已经 OOM。
- 其他 exclude 预设或自定义排除:只排除所列的字段/引用类型,具体检查参数。只排除 weak/soft,不代表其他非强引用路径也全部消失。路径查询参数
- Merge Shortest Paths to GC Roots:合并一个或多个对象到 GC Roots 的最短路径,方便发现共同持有者。它不枚举所有保留路径,所以消除一条最短路径后,仍需检查其他路径。最短路径说明
Debug Bitmap
Bitmap 的内存主要取决于解码后的像素尺寸、配置和实际分配,不能仅由屏幕是“2K”推断。以紧密排列的 ARGB_8888 像素为例,2560 × 1440 × 4 = 14,745,600 字节,约 14.06 MiB。复用后的分配容量还可能大于当前图像需要的容量,API 19+ 可查看 getAllocationByteCount()。Bitmap API
从 MAT 导出 Bitmap 的 byte[] 是有条件的历史方法:快照中必须实际保留像素数组,并知道该实现的像素布局。Android 8.0 / API 26 起像素存储回到 native heap,不能假设每个 Bitmap 都能在 Java HPROF 中找到可直接还原的数组。原操作流程保留在还原 MAT 中的 Bitmap 图像一文,使用前先检查版本和数据是否齐全。
Debug ArrayList
在一个 ArrayList 实例上选择 List Objects → with outgoing references,可以检查它引用的内部存储和其他字段:

这里展开的是内部数组,逐项翻起来有点不方便。数组容量还可能大于集合的逻辑元素数。字段名和实现也可能随运行时变化。对于 MAT 支持的集合实现,可在 Java Collections → Extract List Values 中直接查看元素:

结果更便于阅读:

这个查询依赖 MAT 对集合布局的识别,不是任意自定义容器都自动支持;无法识别时,回到实际类和字段检查。集合查询说明
Big Drops In Dominator Tree
打开 Query Browser / Search Queries,搜索 Big Drops in Dominator Tree 并在当前快照运行,查看内存集中保留的位置。官方查询说明
它关注 retained size 在支配树父子层级间明显下降的地方,帮助找到由一个对象集中保留许多对象的情况,也可能列出较大的数组。图中的 Accumulation Point 是候选聚集点,Dominator 是与之对应的支配者,不能直接理解成 Java 字段上的父对象。

下一步应检查候选对象的内容和容量,再通过 incoming references、Path to GC Roots 找到所有者,结合生命周期判断是否异常。大缓存可能完全符合设计,小对象也可能因生命周期错误而泄漏;Big Drops 提供调查起点,不自动替你下结论。
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
