简介Eclipse MAT全称Eclipse Memory Analyzer Tool即Eclipse内存分析工具是Java开发者定位内存泄漏、分析Java虚拟机堆转储文件的重要工具。压缩包内提供了Windows下可直接运行的MAT环境内含主程序、批处理脚本、配置文件和插件库解压后无需额外安装开箱即用即可加载堆转储文件展开分析。通过泄漏嫌疑报告、支配树、直方图、重复对象检测及引用链分析等功能可迅速找出占用内存过大的对象及GC根对象路径适合本地调试、性能调优和线上内存溢出后的故障排查。包内共5442个文件以3758个html说明文档、422张png示意图、355个jar插件包、247张gif动态演示及109个xml配置文件为主完整工具链约133.56MB目录结构清晰既可通过图形界面程序启动也可用命令行脚本调用使用灵活。已有365人学习下载适合熟悉Java虚拟机内存机制的中高级Java开发者也是提升应用稳定性与运行效率的实用工具。1. eclipse mat 是什么它不读日志它读堆那一瞬间的对象图线上报java.lang.OutOfMemoryError时很多人的第一反应是去翻应用日志grep 错误堆栈、找异常接口、对时间线。这套做法最多只能看到“哪段代码在什么时候触发了 OOM”看不到堆被谁占满。真正能回答这个问题的是 JVM 崩溃前留下的.hprof堆转储快照而eclipse matEclipse Memory Analyzer通常简称 MAT就是专门啃这种文件的工具。先把这个易误解的认知掰正MAT 不是文本日志分析器你不需要拿它去读 logback 日志或 ELK 里的报错信息它的输入是 JVM 堆的二进制快照输出是一张“谁在持有哪些对象、谁该为内存增长负责”的关系图。适合的人群很明确被 OOM、GC 频繁、堆内存只涨不降折腾过的后端、安卓开发和中间件维护者。2. 为什么日志排查不到 OOMeclipse mat 却能一眼定位堆模型与三项核心指标2.1 堆转储文件里存的是对象图不是一行行文本.hprof文件在真机上往往有几百 MB 甚至几个 GB用记事本打开只能看到JAVA PROFILE 1.0.2这样的文件头后面全是乱码。它存的不是日志文本而是某一时刻 JVM 堆的完整快照所有对象实例、对象之间的引用关系、每个对象的类信息、基础类型数组的字节内容以及 GC Roots 的引用起点。所谓“GC Roots”可以粗略理解为 JVM 判定对象是否活着的根静态变量、活跃线程的栈帧局部变量、JNI 引用都属于 GC Roots。一个对象如果能从某个 GC Root 沿着引用链被触达就不会被回收。MAT 做的事情是把这一堆二进制快照还原成一张有向引用图。你在界面上点开一个对象能看到它引用了谁、又被谁引用引用链一直可以回溯到 GC Root。这正好回答了日志回答不了的问题一个 200MB 的byte[]到底是被业务线程持有还是被某个缓存组件持有还是挂在 static 字段上一直没释放。日志只会告诉你 OOM 发生在第几行而这类问题发生前往往已经悄悄攒了很久日志的异常点根本不是你真正的内存大户。2.2 三个核心视图Histogram、Dominator Tree、GC RootsMAT 打开 dump 后等待解析完成后最常用的入口是三个这三个切片各解决一类问题。第一个是Histogram直方图按类汇总实例数量和浅堆大小。比如某个char[]有 8000 个实例、占了 1.2GB它会排名第一。这个视图的用途是“找数量级”哪类对象几千个、几万个总量一下子把堆撑爆。它适合做初筛但不适合下结论。第二个是Dominator Tree支配树它按“支配关系”重新组织对象。简单说如果对象 A 被回收后对象 B 也一定可以被回收那 A 就支配 B。支配树会把整个堆整理成一棵树越靠近树的根部越可能是内存被占用的实际责任者。在 Dominator Tree 里按 Retained Heap 排序通常能看到几个大瘤子的清晰层次。第三个是Path to GC Roots到 GC Roots 的引用链选中任意对象右键这条路径MAT 会列出从该对象一路上溯到某个 GC Root 的所有引用。这是判定“泄漏”的临门一脚如果一条引用链最终落在一个业务对象、静态集合或长期存活的线程上基本可以断定这条链上有人持有不该持有的东西。需要提示的是在弹窗里默认会勾选exclude weak/soft references一般保持勾选否则分析结果会被缓存、代理类等弱引用大量干扰。这三个视图对应三句话什么类占用多 → 谁支配了这个占用 → 谁从 GC Root 把它拉住了。排查路径基本就是按这个顺序走。2.3 retained size 与 shallow size选错指标会把你带偏在视图里能看到两列数字Shallow Size 和 Retained Size。Shallow Size 是指对象本身占用的内存对象头、字段、引用本身不含它引用的那些子对象。Retained Size 才是“这个对象被回收后能真正释放出来的堆内存”也就是它以及它支配的所有对象的总大小。实际分析时Shallow Size 大不代表它是元凶。典型的例子是java.lang.String每个 String 内部引用一个char[]那个char[]的 Shallow Size 巨大但完全可能被 8000 个 HashMap Entry 共同引用着单独释放其中一个根本没有用。反过来一个业务单例对象 Shallow Size 很小但它通过集合持有了一整棵对象树Retained Size 可能是几个 GB这才是重点。所以我的固定习惯是Histogram 里先看 Shallow 找出大头紧接着切到 Dominator Tree 按 Retained 排再用 Path to GC Roots 确认持有链。只盯着 Shallow Size 排名的团队经常折腾一晚上把byte[]和缓存误伤一遍。视图解决问题常用排序字段Histogram初筛哪类对象数量爆炸Shallow Size / ObjectsDominator Tree定位谁实际支配大块内存Retained HeapPath to GC Roots定性引用链是否指向 GC Root层级Leak Suspects自动嫌疑报告置信度3. 从拿到 dump 到定位泄漏eclipse mat 的完整操作路径3.1 用 jmap / jcmd 抓取 dump 文件做内存分析的第一步是拿到一份合格的 dump。实际环境里最常见的做法有两种一种是在 JVM 启动参数里预先埋好-XX:HeapDumpOnOutOfMemoryError让 JVM 在 OOM 发生的瞬间自己把堆快照写盘另一种是问题已经发生、进程还活着时手动抓。手动抓时我用jcmd更推荐跨 JDK 版本的兼容性比jmap好命令如下。# 推荐用 jcmd 抓完整堆快照不附带 Full GC jcmd pid GC.heap_dump /data/dump/tomcat-$(date %Y%m%d-%H%M).hprof # jmap 旧写法-dump:live 会先触发一次 Full GC 再取存活对象 jmap -dump:live,formatb,file/data/dump/tomcat-live.hprof pid # 不用 live 参数保留完整堆里所有对象文件更大但更贴近真实 jmap -dump:formatb,file/data/dump/tomcat-full.hprof pid三个命令的区别要分清-dump:live在抓之前先做 Full GC所以快照里只有存活对象dump 文件小但那些“本来可以回收却被某些引用卡住”的对象反而更容易暴露适合做泄漏分析不加live是完整堆快照包含所有对象适合分析堆整体构成。线上抓 dump 要挑低峰期因为live触发的 Full GC 会造成应用停顿尤其大堆环境下停顿可能以秒计。另外强烈建议把下面这两行参数直接加到 JVM 启动参数里这比任何事后抓取都可靠-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump/oom-default.hprof加上之后OOM 发生瞬间会留下第一手快照文件路径固定、时间点精准不会出现“进程已经被 restart 策略拉起、现场全丢”的窘境。抓完 dump 先用du -sh看一眼大小确保磁盘有余量、文件不是 0 字节。3.2 配置 MAT 启动参数与打开 dump 的选项MAT 有独立发行版不强制装 Eclipse IDE。下载页一般叫 “Eclipse Memory Analyzer / MAT Standalone”按系统选对应包即可安装本质就是解压到一个目录。Windows 64 位环境就找名字里带 win64 的 zipLinux 下解压后直接执行MemoryAnalyzer脚本。启动之前先改一个关键配置文件MemoryAnalyzer.ini否则大 dump 解析到一半可能先把自己 OOM。# 这是 MAT 安装目录里的 MemoryAnalyzer.ini修改后重启 MAT 生效 -Xmx4g -XX:MaxMetaspaceSize1024m-Xmx是 MAT 自己用来解析 dump 的最大堆内存。解析一份 4GB 的 dumpMAT 自身堆内存建议给到 4GB 到 6GB也就是 dump 大小的 1.2 到 1.5 倍左右太保守会直接报Java heap space。这里有个容易翻车的细节MemoryAnalyzer.ini是给启动脚本读取的 JVM 参数文件改错行号会导致 MAT 起不来。我一般只改-Xmx和-Xms其余保持默认。如果你的分析机只有 8GB 物理内存就优先挑小 dump 分析不要硬开。启动后选择File → Open Heap Dump双击打开.hprof文件。第一次打开会询问是否接受安全警告选 OK 即可。解析大文件时右下角有进度条这段时间不是卡死耐心等。打开完成后默认落在 Overview 页最上面的Leak Suspects区域会给出“这个 dump 里最可疑的内存占用”报告适合快速上手但别直接把它当最终结论。万一没有图形界面也可以用命令行的方式直接导出报告出报告后下载 HTML 到本地看# 在 MAT 安装目录下执行利用 headless 模式导出 HTML 报告 ./MemoryAnalyzer -consolelog \ -application org.eclipse.mat.api.export \ -input /data/dump/tomcat.hprof \ -output /data/report/tomcat-leak.html \ -format html这种干法适合线上只能生成 dump 但没法开 GUI 的机器。不同 MAT 版本的 headless 参数可能有差异以你本机执行MemoryAnalyzer -help的输出为准。3.3 按“Histogram → List objects → Path to GC Roots”三步定位有了 dump 之后不要一上来就点 Leak Suspects按固定三步走每一步选对参数能少走很多弯路。第一步打开Histogram在顶部过滤框里输入可疑类名或包名。比如怀疑是大对象列表先看byte[]和char[]的总字节数怀疑是集合直接看java.util.HashMap$Node或ArrayList的实例数。这步的目标是找到 Shallow Size 最大的前三名锁定是哪类对象吃内存。第二步右键其中一个类选List objects → with outgoing references列出这个类的所有实例并且能看到每个实例引用了谁。排序时把 Retained Heap 列出来挑最大的几个实例展开观察它们的内部结构。这一步能识别出“明明只是个小对象却带了一个巨大数组”的情况。第三步对最可疑的实例右键选Path to GC Roots → exclude all weak/soft references看引用链最终挂在哪个 GC Root 上。三步走完结论应该能落成一句话某个业务字段持有了一个大集合集合里的数据来自某个没清理的内部缓存或者某个static的 Map 被不停 put 且没有过期策略。Leak Suspects 的嫌疑对象可以拿来相互印证但不要跳过手动验证。4. eclipse mat 常见问题与避坑翻车最多的 5 个场景4.1 把 .hprof 当文本日志用编辑器直接打开现象下载或拷贝了 heap dump 后有人直接双击.hprof用 IDE 或文本编辑器打开结果要么卡死、要么满屏乱码还会产生一个超大的缓存文件把内存占满。原因.hprof是 JVM 规定的二进制堆快照格式头部的魔数JAVA PROFILE后面全是二进制对象引用本身就不是给人直接看的文本也没有可搜索的堆栈字符串。它和jstack生成的线程快照、业务日志完全是两类东西。解决第一步先明确文件来源确认是jmap或-XX:HeapDumpOnOutOfMemoryError产出的 dump第二步直接丢给 eclipse mat 打开不要在任何文本编辑器里碰它。4.2 MAT 自身 OOM内存配置没跟上现象打开一个 5GB 的 dump解析到一半弹java.lang.OutOfMemoryError: Java heap space或者直接退出。原因MAT 解析文件时需要建立对象索引这部分工作占用 JVM 堆默认MemoryAnalyzer.ini里的-Xmx往往只有不足 1GB遇到大 dump 必然不够。解决修改安装目录下MemoryAnalyzer.ini把-Xmx调到 dump 文件的 1.2 至 1.5 倍。比如 dump 是 4GB就给-Xmx5g。改完启动不会立刻报错。解析前先确认机器物理内存够否则该换机器或改用 headless 导出报告用更小的内存开销定位线索。4.3 jmap 抓 dump 失败attach 机制与用户权限不一致现象执行jmap -dump:formatb,file... pid时输出Unable to open socket file或well-known file is not secure随后抓取失败。原因JVM 的 attach 机制会读取/tmp下以hsperfdata_用户名命名的目录也要有权限访问目标进程的 attach socket。当你是 root目标 Java 进程是普通用户启动或目标 JVM 加了-XX:DisableAttachMechanism就会失去 attach 权限。解决尽量与目标进程同用户执行 jmap/jcmd比如sudo -u appuser jcmd pid GC.heap_dump ...。如果目标进程明确启用了DisableAttachMechanism只能通过修改启动参数添加-XX:HeapDumpOnOutOfMemoryError并重启进程才能拿到 OOM 自动落盘的 dump。别指望在进程运行中硬抓。4.4 把 jstack 线程栈日志误当成堆快照现象MAT 打开文件时报文件格式不支持或者报 “File does not contain a valid heap dump”。原因用的采集命令不对。jstack pid得到的是线程栈文本jstat得到的是统计数字jmap -histo也只是对象计数都不是完整的堆对象快照。只有jmap -dump或jcmd GC.heap_dump生成的文件才能被 MAT 解析。解决检查采集命令。需要对某个进程做内存分析就用jcmd pid GC.heap_dumpjstack单独用于线程死锁排查两者用途完全不一样。拿到 dump 后也可以先用head -c 64看一眼文件头确认有JAVA PROFILE标记再做分析。4.5 把 Leak Suspects 的疑似结论直接当最终结果现象Leak Suspects 报告里写 “Problem Suspect 1” 是一个java.util.HashMap,占了多少 GB于是直接去代码里把 HashMap 相关逻辑找了一遍最后发现真正的问题是另一个线程池缓存了byte[]。原因Leak Suspects 是启发式分析它按支配树自动找最大的被支配对象再把一组对象聚合成嫌疑点本质是一个“猜得比较准”的向导不是案发现场的录像。没有经过 GC Root 链路验证的嫌疑对象只能当线索。解决Leak Suspects 每条嫌疑都点开你会看到一张“Shortest Paths To the Accumulation Point”的引用图。这时要用 3.3 里的三步手动复核确认引用链末端是真正的长期持有者。如果嫌疑对象是byte[]、char[]这类只是被“大而多”堆出来的数据体优先怀疑它被哪个集合或线程缓存拉住不要想着去业务代码里找这个数组的创建点那是缘木求鱼。还有一种典型混淆应用进程带着 Tomcat 报找不到或无法加载主类 org.apache.catalina.startup.bootstrap根本无法启动这时候就算抓了 dump 也没有意义。这个错误属于环境、JDK 或者部署结构问题和环境变量冲突、lib 目录不完整有关跟堆内存分析是两个阶段的问题。先把进程拉起来、确认稳定运行再讨论 MAT 分析才有价值。5. 让 eclipse mat 从“应急工具”变成“日常巡检”OQL 批跑与三向验证5.1 用 OQL 批量跑常见内存大户反复在 GUI 里点 Histogram 很费时我会把高频检查固化成 OQL 脚本每次拿到新 dump 先跑一把结果就是一份可疑对象排名。MAT 的 OQL 语法类似 SQL区别是字段前带的是一些内部属性比如retainedHeapSize表示保留堆大小usedHeapSize表示浅堆大小。一个完整的字符串内存排名查询长这样SELECT s.toString() AS stringValue, s.retainedHeapSize AS retainedBytes, s.usedHeapSize AS objectBytes, s.objects AS instanceCount FROM java.lang.String s ORDER BY retainedBytes DESC LIMIT 30这段 OQL 会把堆里所有java.lang.String实例按 Retained Size 倒序排找出最大的字符串对象。不同 MAT 版本对toString()这类方法调用的支持略有差异如果报语法错误把s.toString()去掉只看retainedBytes排序也能定位大对象。查char[]、byte[]排名更简单SELECT c.retainedHeapSize AS retainedBytes, c.usedHeapSize AS objectBytes, COUNT(c) AS cnt FROM byte[] c ORDER BY retainedBytes DESC LIMIT 20执行入口在 MAT 右上角工具栏的 OQL 图标或者Query Browser → OQL。建议把这些查询保存成一个.oql文件下次换机器直接导入。5.2 三向验证报告、支配树、GC Roots 结论一致才算定案现在我的验证矩阵是三条线同时看Leak Suspects 给的嫌疑点、Dominator Tree 按 Retained 排序的 Top 节点、OQL 跑出来的大对象列表。三条线的重合度越高结论就越可靠。如果只有 Leak Suspects 说某个对象多但 Dominator Tree 里没有它、GC Root 引用链也断在普通缓存引用上那多半是误报或“量大但并不可怕”的正常缓存。一条链路的最终确认标准是引用链上的每一个对象都能对应到一段业务代码且这段代码没有释放或清理逻辑。比如链路上出现ThreadLocal、static Map、Session这类生命周期很长的对象说明问题基本坐实如果引用链末端是 JVM 自己的ClassLoader、Class对象先怀疑是不是类加载器泄漏或 JDK 版本兼容问题。把 MAT 集成进日常的做法不复杂JVM 启动参数里常驻HeapDumpOnOutOfMemoryError线上进程一旦 OOM 自动落盘脚本把 dump 文件按时间戳归档分析机上用 headless 模式直接出 HTML 报告再配合这几条 OQL 把报告里的 Top 对象跑成表格。我处理内存问题的习惯已经变成拿到 OOM 触发点后最多花十分钟看日志确认是什么接口打的剩下的时间全交给 dump 和 MAT。很多被日志文本绕晕的内存问题在对象引用链上一两分钟就现了原形。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?