EN

Android 内存优化之二 - MAT使用进阶

字数统计: 2.6k阅读时长: 9 min
2015/04/11
loading

本文是 MAT 工具使用系列的第二篇,这个系列共三篇,详细介绍了如何使用 MAT 来分析内存问题,既可以是 Java 应用的内存问题,也可以是 Android 应用的内存问题:

  1. Android 内存优化之一:MAT 使用入门
  2. Android内存优化之二:MAT使用进阶
  3. Android内存优化之三:打开MAT中的Bitmap原图

技术复核(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 历史抓取界面

当前流程以 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 反而可能删掉需要的线索。下面是旧版按钮位置:

旧版手动触发 GC

Unreachable对象

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

Unreachable Objects 入口

Unreachable Objects Histogram

MAT 默认解析会从主对象图移除这些对象,保留类名、数量和 shallow size 的汇总,因此这个 Unreachable Objects Histogram 背后没有可逐个遍历的完整引用图。官方说明

旧图展示了 Calculate Retained Size 的菜单:Minimum 是较快的下界估计,Precise 用于精确计算对象集合的保留大小,两者不应混用。

Calculate Retained Size

旧版 Unreachable 表中的 Retained Heap

图中的 Retained Heap 列为 0,是这张旧汇总表的显示结果,不表示这些对象没有占用内存,也不能推广成“所有不可达对象的 retained size 永远为 0”。在保留了对象图的解析方式下,分析条件已经不同。

如果要继续看这些对象的引用关系,可按官方步骤重新解析:

  1. 关闭快照。
  2. 在 Heap Dump History 中对该快照执行 Delete Index Files,只移除可重建索引,保留原 HPROF。
  3. 启用 Preferences → Memory Analyzer → Keep unreachable objects。
  4. 重新打开文件,等待重新解析。
  5. 运行 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。

Histogram group by package

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

Histogram 分组

Thread信息

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

Thread

从旧图中可以继续调查两类线索:

  1. 保留较多对象的线程。查看它的任务、局部引用和上下文是否仍有用途。这里的 retained heap 不能当作该线程的 native 栈总大小。

    线程保留的堆对象

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

    线程数量

帮助信息

在结果项上右键可以打开分析选项:

右键选项

通过 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,可以检查它引用的内部存储和其他字段:

Outgoing

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

Extract List Values

结果更便于阅读:

Extract List Values Result

这个查询依赖 MAT 对集合布局的识别,不是任意自定义容器都自动支持;无法识别时,回到实际类和字段检查。集合查询说明

Big Drops In Dominator Tree

打开 Query Browser / Search Queries,搜索 Big Drops in Dominator Tree 并在当前快照运行,查看内存集中保留的位置。官方查询说明

它关注 retained size 在支配树父子层级间明显下降的地方,帮助找到由一个对象集中保留许多对象的情况,也可能列出较大的数组。图中的 Accumulation Point 是候选聚集点,Dominator 是与之对应的支配者,不能直接理解成 Java 字段上的父对象。

Big Drops In Dominator Tree

下一步应检查候选对象的内容和容量,再通过 incoming references、Path to GC Roots 找到所有者,结合生命周期判断是否异常。大缓存可能完全符合设计,小对象也可能因生命周期错误而泄漏;Big Drops 提供调查起点,不自动替你下结论。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. Java的内存泄露的特点
  2. 2. MAT使用技巧进阶
    1. 2.1. 使用Android Studio Dump内存文件
    2. 2.2. Unreachable对象
    3. 2.3. Histogram
      1. 2.3.1. Thread信息
    4. 2.4. 帮助信息
    5. 2.5. Debug Bitmap
    6. 2.6. Debug ArrayList
    7. 2.7. Big Drops In Dominator Tree
  3. 3. 关于我 && 博客