简介:MAT(Memory Analyzer Tool)即Eclipse内存分析工具,是Java堆内存排查利器,适合Java开发、运维及性能调优人员,用于定位内存泄漏、查看对象引用链、优化JVM内存表现。该压缩包提供完整的MAT独立运行环境,共5442个文件、约133.56MB,含Windows可执行程序、批处理启动脚本、核心jar插件、ini/xml配置文件,并附带hprof示例堆转储文件;其中html帮助文档超过3700个、png/gif示意图近700张,便于对照学习。目前已有365人学习下载。包内除图形界面主程序外,还提供ParseHeapDump.bat命令行脚本和eclipsec.exe命令行入口,可在无界面或服务器环境下批量分析堆快照;自带hprof示例可直接演练Leak Suspects报告、支配树、直方图、重复对象检测、引用链分析等核心功能,帮助读者快速掌握从堆转储生成到定位内存问题的完整流程,并能结合配置文件和二次开发插件进一步扩展分析能力,适合用于实际项目的性能诊断与内存优化。
1. eclipse mat不是日志分析工具,它是堆转储的“法医”
先说结论:eclipse mat的全称是Eclipse Memory Analyzer Tool,名字里带Analyzer,但分析的不是日志,而是Java进程的堆转储文件(heap dump)。你搜“eclipse mat日志分析工具”多半是被“分析工具”四个字带偏了——日志分析该用ELK、用loki、用grep,而mat解决的是另一类更头疼的问题:线上Java服务突然OOM、内存涨上去降不下来、跑两三天必挂一次。这类故障看日志只能看到OutOfMemoryError和一堆堆栈,真正有用的现场在堆里,而mat就是干这个的:把堆转储文件加载进来,告诉你对象是怎么组织的、谁占了多少内存、谁被谁引用着不敢释放。
这篇文面向两类人:一类是刚接手Java服务、遇到OOM只会重启的运维和开发,需要一套从生成堆文件到看懂报告的操作路径;另一类是已经被内存问题磨了几天、怀疑某个缓存或线程池有问题的熟手,需要知道怎么用mat的直方图、支配树和OQL把疑点钉死。后者可以直接跳到第5章和第6章,前者建议从第2章顺着走。整个流程我们会覆盖:怎么拿到一份不脏的堆转储、怎么装对mat版本、怎么从四个核心面板里读出结论、以及五个让分析结果变形的实操坑。
2. 先拿到一份干净的堆转储:三种取证方式与适用场景
2.1 内存溢出前,先让JVM把现场留下来
看到OOM再去找现场已经来不及了——进程可能是被守护进程重启的,堆没了,线索也没了。正确的做法是把取证的开关提前打开。在JVM启动参数里加两个标志,这是所有Java服务启动时都应该有的“后悔药”:
java -Xmx2g \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/data/dump/heap.hprof \ -jar your-app.jar这段参数的意思是:当JVM因为堆内存耗尽而抛出OutOfMemoryError时,先别急着崩溃,把当前的堆内容快照写入指定路径。HeapDumpPath指向的目录要提前建好,并且保证写权限,否则JVM会静默跳过dump,这个参数就白加了。注意这个开关默认是false,很多发行版的基础镜像里并不会帮你打开。
与之配套的另一个参数是-XX:+ExitOnOutOfMemoryError,它让JVM在OOM的时候主动退出而不是继续半死不活地运行。生产环境里我建议两个开关同时开:一个保现场,一个防僵死。需要留意的是,HeapDumpOnOutOfMemoryError生成的hprof文件大小约等于当前堆使用量,2G堆对应的转储文件可能达到1.5G以上,磁盘要留够。
2.2 最小复现:手动导出堆转储的完整命令
线上问题不一定等到OOM才需要排查,很多场景是内存稳步上涨、GC越来越频繁,这时候你需要在进程还活着的时候手动抓一份。jmap是最常用的工具,和JDK一起分发,不需要额外安装:
# 先看进程ID jps -l # 导出整个堆(推荐) jmap -dump:format=b,file=/tmp/heap-$(date +%Y%m%d-%H%M%S).hprof <pid> # 只导出可达对象(慎用) jmap -dump:live,format=b,file=/tmp/heap-live.hprof <pid>注意区别:不带live的是完整堆快照,包含所有对象,哪怕是已经失去引用、等待GC的“垃圾”也还在里面。带live参数会先触发一次Full GC,只保留存活对象。排查内存泄漏时,存活对象才是关注重点,但带live会把“浮动垃圾”过滤掉,导致你看到的内存比实际占用小很多。我一般先抓一份不带live的完整快照看总量,再抓一份带live的对比,两份文件放一起分析,能看出哪些对象一直活着没被回收。
另外,如果jps列出的进程ID和你ps aux看到的对不上,优先信jps。因为同一个Java程序可能用不同用户启动,别人看到的PID和你能访问的PID不是一回事。jmap导出时如果报Unable to open socket file,说明当前用户的权限不够,需要切到进程属主去执行。
2.3 三种方式对比:OOM自动、jmap手动、MAT自动获取
刚才说的两种是主流做法,这里再补一个mat自带的获取能力。如果你本地调试时怀疑某段逻辑有问题,可以直接在mat的界面里连接正在运行的JVM进程抓取,省去敲命令的步骤。在mat的File菜单里选中Acquire Heap Dump,输入主机名和端口就能抓。但这个能力依赖JMX,而且生产环境通常不会把JMX端口暴露出来,所以它更适合本地开发环境。
三种方式的适用场景简单区分:
| 获取方式 | 适用场景 | 注意点 |
|---|---|---|
| HeapDumpOnOutOfMemoryError | 生产环境无人值守,提前埋点 | 两参数要配齐,目录要预建 |
| jmap手动导出 | 内存已经异常,需要立即取证 | 不要带live,先抓完整快照 |
| mat界面远程获取 | 本地开发调试,自己有JMX权限 | 线上端口基本不开放,别指望 |
我自己的习惯是:线上服务的启动脚本里永远带着HeapDump开关,然后监控系统发现内存异动时,通过运维平台触发jmap再补一份“活着的时候”的现场。两种快照时间点不同,比对起来更能说明问题。
3. 装对eclipse mat:版本选择、JDK兼容性与三个启动参数
3.1 独立版和Eclipse插件版该选哪个
eclipse mat有两个分发形态:一个是独立安装包,下载解压就能跑;另一个是Eclipse IDE里的插件,需要先装好Eclipse再安装MAT插件。搜索“eclipse mat下载”会看到Memory Analyzer的官方下载页,提供standalone版本和update site(插件更新源)。没有Eclipse开发环境的人直接选standalone,这是绝大多数情况下的合理选择。装了Eclipse且只在IDE里做事的人,可以用update site方式安装,地址填官方发布页的更新源URL,在Eclipse的Help菜单里走Install New Software流程。
需要明确的是,不管哪种形态,底层的分析引擎完全一样。区别只在启动方式和内存参数配置的入口。插件版对新手有个隐藏麻烦:插件跑在Eclipse的JVM里,Eclipse本身的-Xmx和MAT需要的-Xmx是两套逻辑,经常出现Eclipse还活着但MAT报告打不开的情况。独立版则简单得多,一个启动脚本就能控制。
强烈建议:如果只是做内存分析,别折腾插件版。独立版解压后改一个配置文件就能跑,出了问题也容易排查。如果你常年用Eclipse开发,坚持要插件版,至少要知道Eclipse 2021-06之后内置的JRE被部分JDK发行版替换过,MAT插件与JRE版本不匹配的报错率明显上升。
3.2 安装目录里的两个关键文件:MemoryAnalyzer.ini和mat
以standalone版为例,解压后目录结构大致是这样:
mat/ ├── MemoryAnalyzer.ini # 启动参数配置文件 ├── mat # Linux/macOS启动脚本 ├── mat.exe # Windows启动程序 └── plugins/ # 插件目录第一次打开之前,先改MemoryAnalyzer.ini。mat默认的堆内存上限是1024MB,而你接下来要分析的堆转储文件经常超过这个数。内存给多大取决于分析对象的体量——原则很简单:mat自身可用堆不小于堆转储文件大小的两倍,我习惯设置成转储文件大小的1.5到2倍,上限按本机物理内存来。
-startup plugins/org.eclipse.equinox.launcher_1.6.400.v20211116-1743.jar --launcher.library plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.2.600.v20211117-1030 -vmargs -Xmx4096m -Xms1024m -Xss512m常见做法是改-Xmx这一行。如果你的堆转储是8G,本机内存只有16G,这里设成-Xmx10g会让mat启动很慢,而且加载解析时界面会卡住数分钟。如果本机内存不足,宁可换一台更大内存的机器分析,不要在4G内存的笔记本上硬开16G的文件——不是打不开,是打开后每个操作都像死机,效率反而更低。
有一个容易出现的情况是:你改了Xmx,但启动时提示Could not reserve enough space。这多半是32位JDK惹的祸,换成64位JDK即可。另外确认-Xmx和-Xms不要写成一样的值,分析大文件时起点太高会让启动过程变慢。
3.3 命令行入口验证安装是否正常
独立版的另一个优势是带命令行工具。安装完成后先跑一次命令,确认核心模块没有缺:
# Linux/macOS ./mat -version # Windows mat.exe -version如果提示缺JAVA_HOME,说明系统里没有可用的JDK。mat不是非要最新版JDK才能跑,但JDK版本太新或太旧都有风险。经验值来看,OpenJDK 8、11、17是兼容性最稳的三个区间。如果你本机默认JDK是21或更高,可能会遇到SWT图形库初始化失败,界面起不来。这时候要么装一个JDK 17指向JAVA_HOME再启动,要么在启动脚本里显式指定JDK路径:
# 用picked指定JDK 17位置 JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ./mat命令行验证通过后,再打开GUI,加载一个已知的hprof文件确认分析流程能走通。这一步多花两分钟,能避免真正排查问题时才发现工具链是坏的。
4. 用eclipse mat定位内存泄漏:四个核心面板的读法与先后顺序
4.1 打开堆转储后先看Overview
用mat打开hprof文件,如果一切正常,首先呈现的是Overview页。这一页有两个重要信息:顶部是JVM启动参数和堆配置的摘要,包括总堆大小、使用的收集器;中间是四个操作入口——Histogram、Dominator Tree、Leak Suspects、Top Components,外加一个Actions里的“运行报告”。第一次分析,先别急着点Histogram,先去Overview右侧看“Details”里的“Unreachable Objects”相关统计。
这个细节值得展开:Overview页底部会显示“Total heap size”和“Reachable objects”两行。如果Reachable objects明显比Total heap小,说明堆里有大量不可达对象没被回收——这正是内存泄漏的典型特征:对象已经失去业务引用,但被某种隐式引用(比如ThreadLocal、类加载器、JNI引用)卡在内存里。相比之下,如果Total heap和Reachable几乎相等,问题方向可能是对象本来就该活着,但生命周期太长或缓存膨胀。
我一般先看这个比例,再决定下一步进哪个面板。如果可达对象占比低于70%,走Dominator Tree找源头;如果占比很高,走Histogram按类维度排大小。
4.2 Dominator Tree:找到“持有全局引用的老不死”
Dominator Tree是mat里最有价值的面板,也是排查内存泄漏的核心工具。它按“支配关系”组织对象:一个对象支配了X个对象,意味着它不释放,下面这些全释放不了。面板默认按Retained Heap(浅堆+被它独占支配的堆)降序排列。
实际排查中,前两行通常是这样的:
- 第一行:
byte[],Retained Heap占了几百MB——这说明存在大数组或大量数组存活 - 第二行:某个业务对象,Retained Heap巨大——这才是你想要找的东西
用byte[]做跳板是有道理的:任何Java对象的字符串内容、序列化缓存、IO缓冲最终都存在byte[]里,但byte[]本身不能说明是谁持有它。右键点击那个占位最大的byte[],选Path to GC Roots → with all references,能看到从GC Root到这串数组的所有引用链。顺着链条往上翻,最后一层业务对象才是元凶:可能是缓存Map把不该放的userId放成了key,可能是静态集合里add了对象忘了移除,也可能是某个单例持有了一堆请求上下文。
需要注意一个细节:GC Root引用链里最常见的一个节点是java.lang.Thread的threadLocals字段。看到ThreadLocalEntry别急着定罪,先看它的key是什么。Tomcat线程池里的工作线程都带一堆ThreadLocal,真正有问题的是保存了“应该销毁但没销毁”的业务数据。
4.3 Leak Suspects:给非专业选手的结论报告
如果时间紧或者你不习惯看支配树,Overview页左侧的Leak Suspects Report是傻瓜化入口。mat会执行一组内置的启发式规则,自动找出“嫌疑最大”的支配树子树,并给出说明。执行这个报告会用一两分钟,期间mat下方的状态栏会滚动进度。
打开报告后,顶部通常有一条摘要:类似“The web application appears to have caused a memory leak”或“One instance ofcom.example.CacheManagerretained X MB”。重点是报告中间配的堆栈信息,它会列出可疑对象的最短GC Root路径。这个路径很关键——mat已经帮你做了过滤,剩下的是业务代码里的关键调用点。
不过提醒一句:Leak Suspects的误报率不低。它的启发式规则强在“发现异常大对象”,弱在“理解业务语义”。一个正常的进程内缓存如果数据量大,也会被标记为嫌疑。所以Leak Suspects适合用来“缩小范围”,不适合直接作为定案依据。看到可疑项之后,回到Dominator Tree里人工核验引用链,确认对象确实活着且不该活着,才算坐实。
4.4 Histogram和Thread Overview:两个补充视角
Histogram按类维度统计实例数和占用空间。它解决一个问题:当问题不在单一“大对象”而是“海量小对象”时,Dominator Tree反而不好使。例如String对象数量暴涨——每实例占用可能只有几十字节,但几百万个实例就是几百MB。Histogram第一列是类名,第二列是实例个数,第三列是Shallow Heap,第四列是Retained Heap。先按Retained Heap排序,再从顶部类向下钻取,右键选择List objects → with outgoing references就能看到每个实例的实际内容。
Thread Overview则解决另一个问题:线程对象本身占了多少内存。一个常见的泄漏场景是,线程池不断创建新线程,旧线程没有被回收,每个线程独享的栈空间和ThreadLocal数据累加起来非常可观。Thread Overview按线程列出每个线程的Shallow Heap和Retained Heap,对排查“线程数持续增长”的内存问题帮助很大。
四个面板的使用顺序,个人的习惯做法是:先从Overview看可达对象占比,再跑一遍Leak Suspects快速摸底,然后用Dominator Tree验证嫌疑、顺藤摸瓜找GC Root,最后用Histogram复核是否有同类的小对象堆积。这条路径最多半小时能给出明确结论。
5. eclipse mat使用常见问题:五个让分析结果变形的细节
5.1 堆转储文件太大,mat界面一直卡在进度条
现象:加载几个G的hprof文件时,状态栏一直显示“Calculating retained sizes...”,半小时不动。原因:mat在解析完对象图后还要计算每个对象的保留堆大小,这一步是CPU密集和内存密集的,文件越大越慢。解决:第一,启动前把MemoryAnalyzer.ini里的-Xmx调到最大可用值;第二,如果计算完Retained Size还卡,尝试在Preferences里关闭自动计算快照,具体路径是Window → Preferences → Memory Analyzer → Keep unreachable objects 取消勾选,以及取消自动保存快照。这两个选项能省掉很多不必要的计算量。极端情况下,改用第6章的OQL做定向查询,避免全量计算Retained Size。
5.2 加载转储时报“An internal error occurred during: Parsing heap dump from...”,然后退出
现象:双击hprof文件后,弹窗报内部错误,有的带Failed to load heap dump字样,有的直接让你看日志。原因绝大多数是JDK版本与mat不匹配。mat单个版本只验证过特定范围的JDK,跨版本运行时SWT和解析引擎不兼容的概率很高。解决:检查JAVA_HOME当前指向哪个JDK版本——如果高于17,装一个JDK 11或17再启动mat;如果低于8,升到8u202以上。另一个低频原因是内存不足,但报错文案通常是OutOfMemoryError而不是internal error,所以优先检查版本。
5.3 分析结果里全是byte[]和char[],找不到业务对象
现象:Dominator Tree里排前面的全是数组类型,看起来好像是byte[]泄漏,但项目里并没有人直接new大数组。原因:业务对象往往通过多层引用间接持有了数组,mat默认的排序和聚合把数组单独列了出来,掩盖了实际持有者。解决:不要以数组作为定案对象,在数组上右键执行“Path to GC Roots”,循着引用链一直往上翻,找到第一个项目里的业务类。还有一个技巧是在Histogram上方的“Type Name”搜索框输入业务包名,直接把类过滤出来看这些对象的Retained Heap——一步到位跳过数组这层壳。
5.4 两次抓取的堆转储大小差异巨大,怀疑jmap导出不完整
现象:第一次jmap导出800MB,第二次500MB,差了300MB,看起来像文件损坏。原因:两次抓取时间点不同,堆内浮动垃圾在变化,属正常现象。但如果差到一倍以上,就值得注意是否带上了live参数,或者程序在两次采样之间执行了大批量数据加载。解决:拿两份转储各自的“Total heap size”做对比,而不是比文件大小。文件大小受碎片影响,heap size才是真实数据。如果怀疑jmap中途失败,检查文件末尾是否有完整的hprof头标识,也可以直接放到mat里加载,加载成功说明文件基本是完整的。
5.5 同一个hprof文件,两次打开的结果不一致
现象:第一次打开Dominator Tree里某对象Retained Heap是500MB,关掉重开变成450MB。原因:mat在解析后会把某个快照结果缓存下来,第二次打开时自动复用了缓存,而缓存是在第一次计算后生成的。如果第一次分析时因为操作顺序不同触发了不同的优化路径,结果就有差异。解决:分析前确保Preferences里勾选了自动重建对象快照,或者在关闭文件时勾选“Delete snapshot”清掉缓存。这个问题的发生率不算高,但如果碰上了可能会让你怀疑自己记错了,早点知道机制会省去很多不必要的猜疑。
6. 用OQL在eclipse mat里做定向查询:把定位时间从小时压到分钟
Histogram和Dominator Tree适合“从大到小”找线索,但当你已经怀疑某个特定对象或某种特定数据时,用面板点来点去效率反而低。别忘了mat的工具栏上方有一个“OQL”输入框,这是直接对堆对象图执行查询的入口。以下三个OQL模板是我最常用的基础查询,逐一说明参数含义和调整思路。
// 查询所有字符串长度超过1000的String对象 SELECT s, s.value.length AS len FROM java.lang.String s WHERE s.value.length > 1000 ORDER BY len DESC在OQL里,String.value是char[]类型,所以length取的是字符数组长度。AS len做别名是为了后续排序。这个查询适合定位超大文本缓存,比如从数据库里读出来的长文本被反复缓存。如果你只想查某类前N个,用LIMIT 50收尾。
// 按类统计占用总内存Top 10 SELECT t, SUM(t.retainedHeapSize) AS total FROM OBJECTS t GROUP BY t.@class ORDER BY total DESC LIMIT 10OBJECTS关键字表示对堆中所有对象遍历,t.@class是OQL里获取对象类型的语法。这条语句等效于一个按Retained Heap汇总的直方图,但优势是直接在OQL里完成统计,不触发完整的面板计算,对超大堆更友好。如果你只关心某个包,可以在WHERE里加限制:
SELECT t, SUM(t.retainedHeapSize) AS total FROM OBJECTS t WHERE t.@class.toString().indexOf("com.example.cache") >= 0 GROUP BY t.@class ORDER BY total DESCtoString()方法在OQL中可直接调用,字符串匹配用indexOf代替LIKE是OQL的常见写法,因为OQL的WHERE子句不直接支持SQL风格的通配符。语言不太一样,写不惯的时候翻一下mat自带的OQL帮助文件,里面有完整的语法说明。
最后一个常用的查询是“找出所有持有某个类的实例且该实例没有释放的GC Root路径”:
SELECT * FROM java.net.URL u WHERE u.host.toString() = "example.com"这条用于确认特定外部资源是否堆积。把URL换成你自己的类名或字段名,能快速定位“某个接口地址的对象被反复创建”的问题。
说一个使用OQL时需要养成的习惯:优先在OQL里用LIMIT限制返回行数,因为堆里对象数量动辄百万级,全量返回会让mat的显示层崩掉,表现为界面假死。先跑一个限量查询,确认查询语法没问题,再逐步放开限制。另外,OQL的AS别名和ORDER BY的字段引用必须对应,少写了别名列会导致排序直接报错。多写几次OQL后你会发现,大部分内存问题根本不需要点面板,直接查对象数量和总量就能定位到可疑数据集,配合一次“Path to GC Roots”验证即可收工。
最后说说我个人的工作习惯:拿到一份堆转储,会先在OQL里查一下项目包名下对象的总数和总占用量,形成整体印象,再决定要不要进入面板深挖。如果在Leak Suspects里看到某个嫌疑对象,也会用OQL按类名查一遍它到底有多少实例,避免大报告里的“单实例大对象”掩盖“多实例小对象”的另一种泄漏模式。这套方法帮我节省过很多次无谓的点击,也少走了几次“看完报告还不知道改哪行代码”的弯路,希望也能帮你把内存排查从“玄学”变成“可复现的工程操作”。
本文还有配套的精品资源,点击获取