简介:MemoryAnalyzer-1.6.1.20161125-win32.win32.x86_64.zip 是 Eclipse 基金会开源内存分析工具 MAT 的 1.6.1 版本,面向 Windows 32/64 位平台,用于分析 Java 堆转储文件、定位内存泄漏与高占用对象,适合 Java 开发者和性能调优人员排查内存溢出等场景。压缩包共 463 个文件,大小约 57.99MB,核心为 174 个 jar 插件,配以 html、xml、properties、css 等配置与帮助文档,并包含可执行的 exe、dll 和 ParseHeapDump.bat 脚本,解压后可直接运行。已有 300 人学习使用。借助 Dominator Tree、Leak Suspects 报告、Histogram 和引用链分析,可快速定位大对象持有者与泄漏路径,通过说明文档和脚本即可开展内存诊断与调优,是 Java 服务端问题排查的实用工具。 先说结论:这个包就是 Eclipse Memory Analyzer(MAT)官方发布的 Windows 64 位版本,专治 Java 服务 OOM、内存泄漏、GC 异常这类头疼问题。我过去这几年排查线上内存故障,十个里面有八个是靠它定位出来的。这篇文章就从文件名里的每个字符讲起,覆盖安装配置、四个核心分析功能、一次完整的线上排查实录,以及我踩过的那些坑。不管你是刚接触 JVM 排查的新人,还是已经有了点经验但没用对工具的开发者,这篇文章都能让你少走点弯路。
1. 先拆解这个文件名:一堆字符背后藏着什么
1.1 MAT是什么,为什么它在内存分析里这么重要
JVM 运行期间,所有对象会形成一个庞大的引用网络。所谓内存泄漏,说白了就是一批早该死掉的对象,被某条 GC Root 链路一直拽着,导致 GC 根本回收不掉。Heap Dump 文件就是这个引用网络的“整机快照”,但这份快照动辄几百 MB 甚至几个 GB,里面是上百万个对象,靠人肉翻根本不可能。
MAT 的核心价值并不只是“能把堆倒出来给你看”,而是把“谁最可疑”这件事用算法算出来。它会自动计算每个对象的 Retained Size(保留堆),画出支配树(Dominator Tree),给出从 GC Root 到对象的完整路径,还能生成一份可以直接读的泄漏嫌疑报告。相比 JDK 自带的 jmap、jhat 这些工具,MAT 把分析门槛拉低了一大截,你不需要对 JVM 内部结构有多深的理解,也能顺着线索摸到问题代码。
1.2 文件名的三段式解读
这个文件名不是随便拍的,每个字段都有明确含义:
| 字段 | 含义 |
|---|---|
MemoryAnalyzer | 软件名,Eclipse 基金会下的开源项目 |
1.6.1.20161125 | 版本号1.6.1,构建日期2016-11-25 |
win32.win32.x86_64 | Eclipse RCP 平台标准三元组:OS 为 Windows、窗口系统为 Win32、CPU 架构为 x86_64 |
.zip | 打包格式,Windows 下直接解压 |
这里有个新手很容易踩的误区:看到win32以为是 32 位版本,其实这个三元组是“OS.WS.ARCH”的结构,win32只是指 Windows 的窗口系统标识。真正决定位数的是结尾的x86_64,所以这是一个 64 位版本,需要配 64 位 JDK 才能跑起来。
事件里这个包是 2016 年构建的老版本。现在的 MAT 已经迭代到 1.15、1.16 了,文件名规则还是一样的,只是版本号和日期变了。如果手上是 JDK 11 以上的环境,强烈建议去官网下新版,老版本在解析新 JVM 生成的 Heap Dump 时容易栽跟头,这个我在第 5 节详细说。
2. 下载与安装:从 zip 到一个能用的分析工具
2.1 解压与目录认知
拿到 zip 包后,用 7-Zip 或 WinRAR 右键解压即可。有一点要注意:尽量解压到一个纯英文路径下,比如D:\tools\mat。我早年图省事解压到了D:\工具\mat,结果第一次启动时脚本编码直接乱掉,后来虽然新版没那么敏感了,但没必要拿这种事赌运气。
解压完你会看到这些核心文件:
MemoryAnalyzer.exe:Windows 启动入口。MemoryAnalyzer.ini:JVM 启动参数配置,最重要就是它。eclipse/和plugins/:MAT 基于 Eclipse RCP 平台,运行时依赖都在这里。.metadata/:工作区目录,启动时自动生成,记录了运行日志,启动不了时先看它。
顺便说一句,MAT 还有 Eclipse 插件形式,但独立版更省心,不用为 IDE 版本耦合操心,排查问题的时候开箱即用,我推荐独立版。
2.2 MemoryAnalyzer.ini 里那两个参数决定生死
MemoryAnalyzer.ini在解压根目录下,默认内容大概是:
-vmargs -Xmx1024m-Xmx1024m的意思是 MAT 自己最多用 1GB 内存。如果你拿着一个 2GB 的 Heap Dump 往上一扔,MAT 十有八九会中途报Parsing heap dump failed然后退出。正确做法是分析前先估算一下 dump 有多大,把-Xmx调到 dump 大小的 1.5 到 2 倍,同时不要超过物理内存的 60% 到 70%。举个例子,dump 是 1.8GB,机器内存 8GB,那-Xmx4096m是比较稳的。
还想让 MAT 稳定跑起来,最好把 JDK 位置也显式写进去:
-vm C:/Program Files/Java/jdk1.8.0_202/bin/javaw.exe -vmargs -Xmx4096m-vm后面指定的是javaw.exe的完整路径,要放在-vmargs之前。这样做的好处是防止系统里有多个版本的 JDK/JRE 时,MAT 抓到不兼容的一个。
2.3 双击没反应怎么排查
常见情况是双击MemoryAnalyzer.exe后界面一闪就没了,或者干脆没反应。这时候别干瞪眼,打开根目录下的.metadata/.log文件,里面有完整的异常栈,比乱猜强得多。
根据我遇到过的问题,大概率是这三类:
UnsupportedClassVersionError:JDK 版本太高。MAT 1.6.1 基于 Eclipse 4.6 时代,配 JDK 8 最稳,拿到 JDK 11 以上就会出问题。Failed to create the Java Virtual Machine:32 位 JDK 跑 64 位 MAT,位数不匹配。检查java -version输出里有没有64-Bit字样。- 报
Could not find Java SE Runtime Environment:系统找不到合适的 JDK,用-vm显式指定就好。
3. 核心功能拆解:四个入手点,从自动报告到自定义查询
3.1 Leak Suspects:自动泄漏报告,新手的第一个按钮
打开一个 Heap Dump 后,工具栏上第一个值得点的就是Leak Suspects。这个功能会扫描堆里所有 GC Root 可达的对象,把 Retained Size 最大、最像泄漏源的对象列成一份清单,直接告诉你嫌疑链从哪里开始、谁持有了谁。
不用怀疑,这个功能不是“看起来很好用”,是真的好用。我之前处理过一个 1.8GB 的 dump,点完 Leak Suspects,十几秒就给出了三条嫌疑链,全部指向同一个静态集合。顺着它的提示去做验证,半小时内就确认了问题。
但这里也要泼盆冷水:Leak Suspects 是启发式算法,本质是找“内存里最大的东西”,并不代表一定泄漏。如果代码里本身就有大缓存,它也会把缓存列为嫌疑。所以它的定位是第一道过滤器,下一步必须结合支配树和源码做最终判定。
3.2 Dominator Tree:顺着保留堆找大对象背后的“老板”
支配树这个概念听起来唬人,其实理解起来很简单:如果在堆对象的引用图里,删除对象 A 之后对象 B 也失去了所有引用路径,那么 A 就支配了 B。在实际操作中,你只需要关注每个对象的 Retained Heap,它表示“如果我干掉这个对象,GC 一共能释放多少内存”。
具体操作路径是打开 Dump 后选择Histogram旁边的Dominator Tree,按 Retained Heap 降序排列。看到排名靠前的对象,比如一个 1.2GB 的Object[],先别着急怀疑数组本身——数组只是个容器,你得搞清楚是谁在持有它。右键选择Path to GC Roots,勾选exclude weak/soft references(排除弱引用、软引用),剩下的就是真正的强引用链路,顺着这条链就能找到业务代码里的根。
我见过不少人卡在这一步,原因是在Path to GC Roots时没有排除弱引用和软引用,结果查出来的链路全是缓存框架的 WeakReference,完全看不出问题。记住这个细节,能省你两个小时。
3.3 Histogram:从类维度看内存构成
Histogram 有点像 SQL 里的GROUP BY,它按类维度统计了实例数量、Shallow Heap(对象自身占用)、Retained Heap(对象及其下属对象整体占用)。它回答的问题是“堆里到底什么最多、什么最大”。
当拿到一份陌生应用的 Dump 时,我习惯先用 Histogram 快速打个底:看char[]、byte[]、String这些基础类型的占比,再看有没有业务包下的类异常膨胀。视图上方有过滤框,输入包名前缀可以只显示特定包的类。如果你怀疑某个中间件把响应全部缓存了起来,输入它的包名就能立刻验证。
还有一个容易被忽视的操作:在 Histogram 的任意位置右键,选择Group by Class Loader,可以把对象按类加载器分组。如果某个自定义 ClassLoader 的实例数高得离谱,那很可能就是类加载器泄漏——这在热部署、动态编译场景里非常典型。
3.4 OQL:需要精确制导时写一段查询
OQL(Object Query Language)是 MAT 自带的查询语言,语法很轻,核心就是SELECT FROM WHERE。比如我想看看堆里有没有超过 1024 字符的超长字符串:
SELECT * FROM java.lang.String s WHERE s.value.length > 1024又或者想统计某个业务类里到底缓存了多少个对象:
SELECT toString(), usedHeapSize FROM com.example.report.OrderInfoOQL 不需要背完整语法,常用的就那几个子句。它的存在意义在于:Leak Suspects 和 Dominator Tree 是宏观视角,OQL 则是精准狙击。当宏观分析已经锁定了目标类,写一句 OQL 把同类对象全部查出来,比反复在图形界面里展开树省力得多。
4. 实战记录:一个线上 Full GC 问题的完整排查过程
4.1 现场信息收集
有个内部报表服务,每天早上跑批完成后,9 点开始频繁 Full GC,接口响应从 200ms 一路飙到 10s,接近不可用。我当时按这个顺序做现场取证:
jps -l找到服务进程 PID。jstat -gcutil <pid> 1000 5每秒打一次 GC 信息,确认 Old 区 100%,Full GC 一直在触发但回收效果很差。- 用
jmap抓 Heap Dump:
jmap -dump:live,format=b,file=/data/report_$(date +%Y%m%d).hprof <pid>这里有两个知识点:-dump:live参数会先触发一次 Full GC,只保留存活对象,生成的 dump 体积更小,分析起来更快,但代价是丢掉了已经死掉对象的信息,所以它适合找“谁活着占了内存”的问题,不适合分析“GC 为什么回收不掉”。另外,抓 dump 本身会挂起 JVM 一小段时间,生产环境一定要挑低峰期,或者提前做好降级预案。
4.2 从 Leak Suspects 到定位代码
dump 文件 1.8GB,本机 8GB 内存,我把 MAT 的-Xmx调到 4GB 后打开。几秒钟后界面加载完成,Leak Suspects 的报告很有冲击力:一个java.util.ArrayList实例独自保留了约 1.3GB 内存,并且嫌疑链直接指向ScheduleDataHolder.cache这个静态字段。
切到 Dominator Tree,按 Retained Heap 排序,展开这个ArrayList,里面的元素全是历史订单相关的OrderInfo对象,数量大约是 380 万个。继续右键,选择Path to GC Roots -> exclude weak/soft references,链路最终落在这样一条路径上:
static ScheduleDataHolder.cache -> ArrayList -> Object[] -> OrderInfo问题已经很清楚了:跑批任务每次查询到的订单结果,全都被塞进一个无上限的静态集合,每天增量累加,从不清理。这属于非常典型的内存泄漏写法,静态集合当缓存用,又缺少容量和过期约束。
4.3 修复与验证
修复方案没有用复杂的东西:把静态集合换成 Caffeine 本地缓存,设置最大容量 2000 条、写入后 10 分钟过期;同时在业务侧增加了分页查询逻辑,避免一次性把所有订单加载到内存。
上线后继续观察 GC 指标:Full GC 次数从每天上百次降到了个位数,Old 区占用稳定在 20% 左右,接口响应恢复正常。这里有个经验:修复完不要只看着不报错,一定要压测一轮,确认高并发场景下对象数量达到峰值时内存仍然可控,否则很容易从“静态集合泄漏”变成“缓存框架内存溢出”,那又是另一种解法了。
5. 常见问题与避坑技巧
5.1 dump 文件打不开或解析失败,先分清是格式问题还是内存问题
“MAT 打不开 dump”是我在社区里被问得最多的问题。先看报错文案,如果提示Unsupported format或者文件头无法识别,多半是 MAT 版本太老,解析不了高版本 JDK 生成的 Heap Dump。解决办法是升级 MAT 到新版,或者换用jcmd <pid> GC.heap_dump /path/to/dump.hprof重新导出一次。
如果提示的是Parsing heap dump failed且后面跟着内存相关的栈信息,那就不是格式问题,是 MAT 自己内存不够。调大-Xmx,至少和 dump 文件体积相当,再试一次。另外,如果 dump 文件是在 Windows 下传到 Linux 服务器、或者反过来跨平台传输的,先比对一下文件大小和 MD5,别让损坏文件耽误时间。
5.2 分析大 Heap Dump 时的实用姿势
遇到 10GB 以上的大 dump,本地 8GB 内存的电脑基本跑不动 MAT。我的做法是换一台内存足够的机器,或者干脆在服务器上用 MAT 的命令行模式解析,不需要开图形界面:
./ParseHeapDump.sh /data/heap.hprof org.eclipse.mat.api:suspects org.eclipse.mat.api:overview命令执行后会在 dump 同目录生成分析报告文件,适合把排查过程固化到自动化脚本里。Windows 下对应的是ParseHeapDump.bat,另外 MAT 在解析完大 dump 后会在同目录留下.index索引文件,这个文件损坏会拖慢后续分析,直接删掉让它重新生成即可。
5.3 版本兼容性总结
Crashes 和版本兼容性是很多人没意识到的大坑。简单的对应关系:
| MAT 版本 | 推荐 JDK | 说明 |
|---|---|---|
| 1.6.x | JDK 8 | 老项目常用,解析 JDK 8 的 dump 没问题 |
| 1.13+ | JDK 11/17 | 解析新版 JVM 的 dump 更稳妥 |
| 1.15+ | JDK 17/21 | 当前新项目建议直接用这个范围 |
生产环境抓 dump 用的 JVM 越新,越要用新版的 MAT 去解析。另外抓 dump 前记得看下启动参数里有没有-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump,我在所有 Java 服务的启动脚本里都会默认加上这一组参数,这样哪怕没来得及手动抓 dump,OOM 瞬间 JVM 也会自动留一份现场快照,后面拿着它分析就行。
5.4 排查前先判断问题类型,别一上来就抓 dump
盲人摸象式的抓 dump 也是一种常见低效行为。GC 问题分成两类:对象持续增长、GC 收不掉,这适合用jmap -dump:live做内存分析;而频繁 Full GC 但每次都能回收,这种更像线程频繁创建、JIT 异常或者内存分配速率过高,重点应该看 GC 日志而不是 dump。排查前先用jstat -gcutil观察两三分钟,确认 Old 区和 Full GC 的趋势,再决定要不要打开 MAT,效率会高很多。
个人经验是,MAT 这些功能不要等到线上炸了才开始学。找一台测试机器,故意写一段静态集合缓存数据的代码压一压,生成一份 dump,把 Leak Suspects、Dominator Tree、Histogram、OQL 四个功能各点一遍,下次真遇到问题时你就有底了。
本文还有配套的精品资源,点击获取