简介:Eclipse MAT 内存分析工具完整资源包,面向需要排查 Java 堆内存泄漏、优化 JVM 性能的开发者与运维人员,提供可直接运行的 MemoryAnalyzer.exe、eclipsec.exe、启动脚本及配套插件配置,可快速生成堆转储并解析 hprof 文件。压缩包共 5442 个文件,以 html 帮助文档、png 示意图、jar 插件和 gif 演示为主,另有 xml、properties、dll 等运行支持文件,整体约 133.56MB,附带多个 hprof 示例堆文件与日志,便于结合实例对照练习。已有 363 人学习使用。借助 Leak Suspects 泄漏嫌疑报告、Dominator Tree 支配树、Histogram 直方图等核心分析视图,可快速锁定占用内存过大的对象与引用链,厘清循环引用和重复对象,也可结合对比分析追踪不同时间点的内存变化,是系统掌握 MAT 操作、解决生产环境 OOM 与内存调优的实用资料。
1. 环境准备:拿到MAT之前先把这些坑填平
做Java开发的人,几乎都会撞上一次内存问题:线上服务跑了两周突然OOM、本地调试时GC日志刷屏、压测一上CPU直接飙到100%。我以前排查这类问题也靠猜——先重启试试,再问问运维有没有加堆,实在不行就找研发团队互相甩锅。后来老老实实用上Eclipse MAT(Memory Analyzer Tool),才发现内存问题其实是可以被“看见”的。
MAT是一款专门用来分析Java堆转储文件(heap dump)的工具,它能从巨大的堆文件中找出哪些对象占用了最多内存、哪些引用链把对象“卡”在内存里不释放,还能自动生成Leak Suspects报告,直接告诉你“重点怀疑哪些地方”。它适合所有用Java做后端开发的工程师,尤其是维护过长时间运行的服务、遇到过OOM或内存缓慢增长问题的朋友。相比JProfiler这类商业工具,MAT免费、轻量、启动快,分析离线dump更是它的强项。
先说安装。Eclipse MAT主要有两种形态:一种是Eclipse IDE里面的插件形式,另一种是独立版(standalone)。我强烈建议直接用独立版——日志分析场景下你通常只需要一个快速打开dump文件的工具,没必要为了它专门装一整个Eclipse IDE。独立版下载后解压即用,Windows、macOS、Linux都有对应版本。
这里有一个非常关键的坑:MAT依赖JDK,而且版本必须匹配。以较新的1.15.0版本为例,它要求JDK 17以上;但很多公司的生产环境还停留在JDK 8,dump文件本身是用JDK 8生成的,MAT解析时大多数情况下没有问题,因为dump格式是跨版本兼容的。真正出问题的是MAT自己的启动JVM版本太低或太高导致无法打开。如果你电脑里默认的Java命令指向的是Java 8,启动MAT时可能会直接报“UnsupportedClassVersionError”之类的错误。解决办法很简单:确保启动MAT的Java版本满足要求,或者修改MAT安装目录下的MemoryAnalyzer.ini,在文件开头加上-vm参数指定JDK路径。
另外强烈建议修改MAT的最大堆内存。打开MemoryAnalyzer.ini,找到-Xmx1024m这段配置,生产环境的dump文件动辄几个GB,默认1G内存根本不够用,解析到一半就会弹OutOfMemoryError。我的经验是:dump文件多大,MAT的堆内存至少要设成dump文件的1.5到2倍。比如分析8GB的dump,建议把-Xmx设成-Xmx12g或者更高,同时机器要留有足够空闲内存,不然MAT自己先OOM了,场面会很难看。装好、配好、能正常启动,环境这块才算过关。
2. 获取dump文件:分析的第一步就是把现场完整保留下来
很多人在MAT上栽的第一个跟头不是工具不会用,而是根本没拿到一份合格的dump文件。企业环境里OOM发生后,JVM通常就直接挂了,日志里只剩下一堆堆栈,你想dump都没机会。所以获取dump的时机和方式一定要提前设计好,不能等出了问题再想办法。
最常用的方式是在JVM启动参数中加上自动转储配置:
-XX:+HeapDumpOnOutOfMemoryError:发生OOM时自动生成dump文件,文件默认输出到工作目录,也可以通过-XX:HeapDumpPath=/data/logs/app.hprof指定路径。-XX:+HeapDumpBeforeFullGC:Full GC之前做一次dump,适合排查Full GC频繁导致服务卡顿的场景。- 如果是持续内存增长但还没达到OOM,可以用
jmap -dump:live,format=b,file=/data/logs/app.hprof <pid>手动抓取。注意live参数只导出生还对象,能大幅缩小dump文件体积,但如果怀疑是对象没被回收的问题,导出全量dump更合适,去掉live即可。
在容器环境里还需要留意dump文件写入路径的挂载盘空间。我曾经遇到过一次线上OOM,JVM自动生成dump时因为磁盘写满直接卡死,服务都挂了一分钟。这个场景挺常见的,所以HeapDumpPath指向的目录一定要预留足够空间,一般建议是堆上限的1.5倍以上。
dump文件获取到了,还有一个容易被忽略的点——确认文件完整性。通过ls查看文件大小是否还在变化,用MAT打开时如果提示“File is not a valid hprof file”,多半是dump过程被中断、文件不完整。另外,同一个时间点的dump最好配上当时的GC日志、线程栈,这三件套放在一起,排查效率会高很多。光看一个dump,有些内存问题的全貌看不出来。
还有一个实用技巧:不要在应用还在峰值流量时手动jmap。jmap导出dump会触发Stop-The-World,对处于高并发状态的服务影响很明显,搞不好会拖垮线上接口。建议在低峰期操作,或者直接依赖HeapDumpOnOutOfMemoryError自动转储。对于一个日志分析工具来说,dump就是它的输入数据,输入数据质量不行,后面做再多分析都是浪费。
3. Leak Suspects报告:让MAT替你圈出“最可疑的内存黑洞”
拿到dump并成功在MAT里打开后,很多新手会直接点进Overview看那些饼图、折线图,看半天也看不出结论。这里我推荐一条正确的打开路径:先看Leak Suspects,再验Histogram,最后用Dominator Tree追根因。
在MAT工具栏上点击“Leak Suspects”图标(那个黄色灯泡样式的按钮),MAT会用内置的启发式算法扫描整个dump,自动找出最可能导致内存泄漏的几个嫌疑点。这个报告的价值在于它直接帮你缩小了排查范围——面对一个几GB的dump,你不可能一个个类去看,就需要先让工具帮你圈出一个大概区域。
以一个我处理过的真实场景为例:一个在线教育系统的用户会话服务,本来内存走势还算平稳,结果版本升级后每过两三天就OOM一次。Leak Suspects报告打开后,排在第一位的嫌疑点显示“org.apache.catalina.session.StandardSession实例被java.util.concurrent.ConcurrentHashMap$Node持有,数量达到 86,432 个”,下方还附带了完整的引用链和问题描述。这条信息基本等于直接告诉我:会话对象没被正常失效,堆积在Tomcat的session管理器里了。
那个引用链长这样:
java.lang.Thread @ 0x7a3c98f80 线程池线程 └─ com.mchange.v2.c3p0.impl.NewPooledConnection @ ... └─ java.lang.ref.Finalizer @ ... └─ java.util.concurrent.ConcurrentHashMap$Node @ ... └─ org.apache.catalina.session.StandardSession @ ...当然,Leak Suspects不是神,它是一个基于启发式规则的辅助工具,偶尔会误报。比如有时代码里确实设计了一个全局缓存,里面放了很多数据,这个缓存被当成“System Class”持有,Leak Suspects就会把它识别为泄漏嫌疑点,但实际上这是业务正常的缓存逻辑。所以看到报告不要直接照单全收,把它当成线索,用后面的工具去验证。
对Leak Suspects里的嫌疑点,我做过的有效操作是:点开每个嫌疑点的“Details”,看它展示的具体对象数量、占用字节数、类加载器信息。重点关注两点:一是这些对象是否应该存在这么多个;二是持有它们的类是否是框架或容器类。如果业务代码类直接引用了大量本该短生命周期的大对象,那问题十有八九出在这条引用链上。Leak Suspects报告最好和其他视图交叉验证后再下结论,只看单一报告是做不出确定性结论的。
4. Histogram与保留堆分析:把“占用内存最多”落到具体对象上
Leak Suspects报告告诉你“哪里可疑”,但如果你需要进一步确认问题根因,或者Leak Suspects没给出明确结论,就需要打开Histogram。
Histogram视图展示的是dump中每个类的实例数量、浅堆大小(Shallow Heap)和保留堆大小(Retained Heap)。很多人不清楚这两个概念的区别,我用一个生活化的例子解释:
假设一个公司要裁员(回收内存)。浅堆大小是这个员工自己工位上东西的体积,保留堆大小是这个员工连同他管理的小团队一起被裁掉后释放出来的总体积。
对象之间的引用关系就是这样——对象A引用了对象B,回收A的时候B也可能一起被回收,所以B对A来说就是“保留”的部分。在MAT里排查内存问题时,保留堆比浅堆更有参考价值,因为它反映的是“如果把这个对象清掉,实际能释放多少内存”。
我在排查某个支付网关内存增长问题时,用Histogram按Retained Heap排序后看到结果是这样的:
| 类名 | 对象数 | 浅堆 | 保留堆 |
|---|---|---|---|
| byte[] | 12,345 | 568 MB | 1,024 MB |
| com.example.pay.GatewayRequest | 98,654 | 45 MB | 890 MB |
| java.lang.String | 210,567 | 33 MB | 320 MB |
| java.util.HashMap$Node | 178,900 | 18 MB | 210 MB |
一眼就能看出问题:GatewayRequest这个业务对象居然有接近10万个实例在堆里,而且保留了接近890MB内存。正常情况一个请求处理完,这个对象就该被垃圾回收了,堆里留着这么多,一定是哪里引用它没释放。
接下来配合右键选择“Merge Shortest Paths to GC Roots” → “exclude all phantom/weak/soft etc. references”,过滤掉弱引用、软引用之后,就能看到哪些GC Roots还在强引用这些对象。我那次看到的结果是:一批请求线程的ThreadLocal变量缓存了GatewayRequest,线程复用了,ThreadLocal却没清理,导致每个线程都“扣住”一批请求数据,日积月累堆就被撑爆了。这个问题的根因定位,靠的就是Histogram排序加GC Roots路径分析。
Histogram里还有个很好用的功能是“Group By Class Loader”,这个在排查应用部署多个版本的老项目时特别有用——它能把内存占用按类加载器分开,一眼看出是哪个ClassLoader加载的类占用了大头。如果你用的是Spring Boot内置Tomcat,正常情况下大部分业务类都在同一个类加载器下,如果出现了多个类加载器各占一堆,往往是热部署或fat jar解压路径异常导致的。
5. 进阶排查:几个日常必用的实用小功能
有一句话我一直挂在嘴边:排查内存问题,90%的场景用Histogram、Dominator Tree、Leak Suspects三个功能就够了。但剩下的10%疑难杂症,靠的就是一些进阶功能。
第一个是Dominator Tree(支配树)。它展示的是对象的支配关系——如果一个对象A回收时连带回收了对象B,A就是B的支配者。这个视图适合用来看“谁占着内存”,它自动把长引用链折叠起来,直接展示每个大对象的子树大小。比方说你发现一个java.util.HashMap实例占了1.5GB内存,点开它的子树,就能看到里面装的是哪些key和value,一目了然。
第二个是OQL(Object Query Language)查询。这个适合有明确目标时使用,比如你想确认某个业务对象在堆里到底有多少个实例、每个实例的字段值是什么,可以直接写类似这样的OQL语句:
SELECT t.uri AS uri, t.status AS status, COUNT(*) AS cnt FROM com.example.dto.PaymentOrder t GROUP BY t.uri, t.status ORDER BY cnt DESC这不光能告诉你“PaymentOrder有多少个”,还能按业务字段做维度统计,直接定位到“哪条支付链路的数据积压占用了大量内存”。我排查过一个订单状态机内存泄漏问题,就是靠OQL按订单状态维度分组,发现大量PENDING_PAYMENT状态的订单对象遗留在内存里,顺藤摸瓜找到了没关掉的定时任务洄流Bug。
第三个容易被忽略的功能是Compare Base Line(对比基线)。如果你有多个时间点的dump文件——比如服务刚启动时抓一个、运行8小时后抓一个、OOM前抓一个——可以先后打开这两个dump,在Histogram的工具栏上点击“Compare Base Line”的图标(类似两个扇形叠加的图标),MAT会自动生成一份两个dump间的差异报告。
这份报告会列出新增对象、消失对象、增长幅度最大的类。这个功能在排查“内存缓慢增长”这类问题时价值很大。一次运行3天后OOM的服务,光看最后一个dump,你会发现堆里全是各种缓存,很难判断哪个是元凶;但对比启动时和OOM前的dump,就很容易看到增长的集中在哪个类上。比如我遇到过最典型的一次,对比后发现增长的全是java.net.SocksSocketImpl实例,最终定位到是一个网络连接池的连接对象没被正常关闭,每次请求泄漏一个socket。这种问题,不看增长趋势根本查不出来。
6. 常见问题与排查技巧实录:搜遍全网都不一定找得到的经验
我在用MAT的过程中踩过不少坑,这里挑几个最常见的整理成一张表格,你直接照着排查就行:
| 问题现象 | 可能原因 | 处理方法 |
|---|---|---|
| 启动MAT报错“Failed to create the Java Virtual Machine” | MemoryAnalyzer.ini中-xmx设得过大,或者JDK版本不匹配 | 调小xmx到系统可用内存范围内,或修改-vm指向匹配的JDK |
| 打开dump时一直转圈或直接闪退 | 堆读入内存不足,dump文件大于MAT堆内存 | 调大-Xmx,重启MAT重试 |
| 提示“Unsupported major.minor version” | MAT版本要求的JDK比系统当前JDK版本高 | 升级JDK,或换用兼容的MAT老版本 |
| Leak Suspects报告显示“No suspected leaks” | dump可能是在Full GC后抓取的,垃圾已回收 | 用jmap不带live参数重新抓取全量dump |
| 解析巨大型dump时MemoryAnalyzer崩溃 | 缺少内存或使用32位JVM | 确保64位JDK,xmx设到足够大,关闭其他占内存的软件 |
| Eclipse老项目导入时无法部署 | 项目结构不匹配、缺Server Runtime配置 | 在Eclipse中正确配置Tomcat Runtime,或在Project Facets里勾选Dynamic Web Module |
还有一个很多人不知道的小细节:MAT的Leak Suspects报告默认阈值是85%,意思是“当某个对象集合的保留堆占到整个堆的85%以上时才判定为嫌疑点”。如果你希望它更敏感一些,可以调整MemoryAnalyzer.ini中的相关参数,或者直接看Overview里的“Actions”面板,点击“Calculate Minimum Retained Size”,主动计算每个对象的保留堆大小,再做排序。
另外,我建议在Eclipse MAT安装目录放一份dump分析记录模板.md,每次排查问题时按固定结构记录:服务器IP、应用名、dump时间点、堆大小、Top 5类占用、根因定位、修复方案、修复效果。这看起来有点流水账,但运维和研发扯皮的时候,这就是最有说服力的证据。更重要的是,积累到一定数量后,你会慢慢发现公司的业务代码里哪些框架容易产生内存问题,下次再处理类似故障,速度会快很多。
还有一条我自己验证过很多次的经验:拿到dump先别着急用MAT解析,先看一下文件size。如果dump文件只有几十MB,很可能OOM发生时堆里确实是空的,解析结果往往不说明问题;如果dump文件有2GB以上,一般都有比较明显的增长对象可以看。另外,OOM发生在深夜的话,不一定要立刻恢复服务,先在测试环境复现或保留现场dump,反而对事后排查更有利。用MAT分析dump从来不是目的,目的是通过它把内存问题定位到代码层面,最终修掉那个导致泄漏的Bug。
操作到这一步,你应该已经可以从“拿到dump完全懵”到“先看Leak Suspects → 用Histogram验证 → 追GC Roots → 对比dump找增长趋势”这条路子了。内存排查没有百分之百的银弹,但MAT这套流程配合好的分析习惯,确实能解决绝大部分Java服务端的内存疑难杂症。
本文还有配套的精品资源,点击获取