内存泄漏检测:Memory Analyzer(MAT)详解
内存泄漏(Memory Leak)是 Java 与 Android 应用最棘手的问题之一:不再使用的对象因仍被持有引用而无法被 GC 回收,长时间累积最终耗尽内存,引发卡顿甚至 OOM 崩溃。Memory Analyzer(简称 MAT)是 Eclipse 基金会开源的堆转储分析工具,也是业界定位 Java/Android 内存泄漏的事实标准。它通过解析 hprof 堆快照,帮开发者算出谁占了最多内存、又是谁"拽住"了本该回收的对象。本文系统讲解 MAT 的核心概念、常用视图与定位泄漏的典型流程。
1、MAT 概述
MAT(Eclipse Memory Analyzer Tool)是一个用于分析 Java 堆转储(heap dump)的开源工具,可独立安装,也集成于 Eclipse 和 Android Studio。它能加载动辄几百 MB 的 hprof 文件,自动计算每个对象的内存占用与引用关系,并给出可疑泄漏的报告。
- 自动报告:Leak Suspects 一键给出最可疑的泄漏点与引用链
- 精确计量:区分 Shallow Heap 与 Retained Heap,量化对象真实占用
- 全局检索:Histogram 按类统计,OQL 用类 SQL 语法查询堆对象
- 关系剖析:Dominator Tree 与 Path to GC Roots 直观展示引用关系
参考资料:
MAT分析工具
内存分析工具 MAT 的使用
内存分析工具MAT(Memory Analyzer Tool)的安装与使用
2、内存泄漏与 GC Roots
Java/Android 采用可达性分析判定对象生死:从 GC Roots 出发,沿引用链可达的对象被视为存活,不可达的才会被回收。所谓泄漏,就是某个本该释放的对象,仍能从某个 GC Roots 走到——即被"长生命周期"的引用间接拽住。因此,找泄漏本质上就是找出"对象到 GC Roots 的引用链"。
- GC Roots:虚拟机栈中的局部变量、静态字段、活动线程、JNI 引用等
- 常见泄漏:Handler 持有 Activity、静态变量持有 Context、内部类隐式引用外部类、广播/监听注册未反注册、单例缓存无上限、WebView、集合对象未清理
以最常见的 Handler 泄漏为例:
// 泄漏写法:匿名内部类 Handler 隐式持有外部 Activity
public class LeakActivity extends Activity {
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) { /* ... */ }
};
} 延迟消息会长期持有该 Handler,而 Handler 又持有 Activity,导致 Activity 无法回收。正确做法是使用静态内部类 + 弱引用:
static class SafeHandler extends Handler {
private final WeakReference<Activity> ref;
SafeHandler(Activity activity) { ref = new WeakReference<>(activity); }
@Override
public void handleMessage(Message msg) { /* 通过 ref.get() 取 Activity */ }
}
3、获取堆转储(hprof)
3.1、抓取堆快照
分析前需要先拿到一份堆转储。Android 上有三种常用方式:
// 方式一:Android Studio Profiler
// Profiler -> Memory -> 操作复现场景 -> 点击"Dump Java heap"
// 方式二:命令行 dumpheap(需知道进程 PID)
adb shell am dumpheap <PID> /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof
// 方式三:在代码中主动触发
Debug.dumpHprofData("/sdcard/dump.hprof");
3.2、转换 Android hprof 格式
Android 导出的 hprof 使用的是自定义格式,MAT 无法直接打开,需用 SDK 自带的 hprof-conv 工具转换为标准格式:
// 位于 Android SDK 的 platform-tools 目录下
hprof-conv original.hprof standard.hprof
// 之后用 MAT 打开 standard.hprof 即可
4、MAT 核心概念
4.1、Shallow Heap 与 Retained Heap
这是 MAT 最关键的一对概念,看懂它才能判断谁是"内存大户"。
- Shallow Heap(浅堆):对象自身占用的内存,不含它引用的其他对象
- Retained Heap(保留堆/深堆):该对象被回收后能释放的总内存,即它独占支配的所有对象之和
找泄漏时,应重点关注 Retained Heap 异常大的对象——它往往是"拽住一大片内存"的罪魁。
4.2、Dominator Tree(支配树)
支配树展示对象的支配关系:若对象 A 支配对象 B,说明所有到 B 的路径都必须经过 A,移除 A 即可回收 B。按 Retained Heap 排序的支配树,能一眼定位占内存最大的"根节点"。
4.3、GC Roots 与引用链
MAT 的"Path to GC Roots"会列出对象到 GC Roots 的完整引用路径,这正是泄漏的"持有人"。查看时通常选择"exclude weak/soft references",因为弱引用、软引用不影响回收,排除后剩下的强引用链才是泄漏证据。
5、常用分析视图
5.1、Leak Suspects(泄漏嫌疑报告)
打开 hprof 后,MAT 会提示生成报告。Leak Suspects 自动分析并按嫌疑度给出最可能的泄漏点,配文说明与引用截图。90% 的简单泄漏靠这一张报告就能直接定位,是最省事的入口。
5.2、Histogram(直方图)
按类统计对象数量、Shallow Heap 与 Retained Heap。支持用正则按类名过滤,例如只看自己应用的包名,快速锁定业务对象而非系统类。
5.3、Dominator Tree(支配树)
按 Retained Heap 从大到小列出对象,逐层展开查看其支配的子对象,适合定位"谁占了最多内存"。
5.4、Path to GC Roots
在 Histogram 或 Dominator Tree 中右键某个可疑对象,选择"Merge Shortest Paths to GC Roots"并排除弱/软引用,即可看到拽住它的强引用链。
6、定位泄漏的典型流程
把上述视图串成一条标准排查路径,可以高效定位大多数泄漏。
核心实现:
1. 操作复现场景,随后主动触发 GC,排除正常的临时引用
2. 抓取 hprof,并用 hprof-conv 转为标准格式
3. 用 MAT 打开,先查看 Leak Suspects 自动报告
4. 若报告不够,用 Histogram 按包名过滤出可疑业务类
5. 对可疑对象查 Path to GC Roots(排除弱/软引用),找到持有链
6. 沿引用链回到代码,修正持有关系(改弱引用、及时反注册、切断静态引用等)
7. 重新抓取 dump,验证该对象 Retained Heap 是否回落
7、实战技巧
7.1、按包名过滤
Histogram 顶部有正则过滤框,输入应用包名(如 `com.example.*`)可只看自家对象,屏蔽海量系统类,快速聚焦业务代码。
7.2、OQL 对象查询
MAT 提供类 SQL 的 OQL(Object Query Language),可按需检索堆对象:
// 查询所有 Bitmap 对象
SELECT * FROM android.graphics.Bitmap
// 查询某包下、且尺寸大于阈值的实例
SELECT * FROM com.example.Model WHERE @retainedHeapSize > 10000
7.3、对比两次快照
分别在"操作前"和"操作后"各抓一份 dump,用 MAT 的 Compare 功能对比,可直观看到哪些对象新增、哪些未释放,是定位"增长型泄漏"的利器。
8、总结
MAT 是定位 Java/Android 内存泄漏的利器。它的全部价值都建立在"堆转储 + Retained Heap + 引用链"这三件事上:通过 hprof 看清对象全貌,用 Retained Heap 量化谁占了最多内存,再沿 Path to GC Roots 找到拽住泄漏对象的"真凶"。
关键要点:
- Android hprof 必须先用 hprof-conv 转换才能用 MAT 打开
- Shallow Heap 看自身,Retained Heap 看真实占用,找泄漏看后者
- 简单泄漏看 Leak Suspects,复杂泄漏走 Histogram + Path to GC Roots
- 查看 GC Roots 路径时务必排除弱/软引用
对于 Android 开发者而言,掌握 MAT 不是可选项,而是应对线上 OOM、内存抖动与隐性泄漏的必备技能。把"复现场景 -> 抓 dump -> 看报告 -> 追引用链 -> 回代码修复 -> 复测验证"这套流程练熟,绝大多数内存问题都能在短时间内闭环解决。