ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Eclipse MAT实战:堆转储与支配树定位JVM内存泄漏

Eclipse MAT实战:堆转储与支配树定位JVM内存泄漏 简介这是一套 Eclipse Memory Analyzer ToolMAT的完整发行包面向需要诊断 Java 堆内存泄漏的开发者与性能调优工程师可帮助定位大对象、分析引用链与内存增长趋势。包内不仅包含可独立运行的 MemoryAnalyzer.exe 及配套启动脚本、配置文件还附带了数百个插件 jar 与文档 html覆盖 MAT 插件体系、帮助文档和示例资源方便离线部署或深入学习。压缩包共 5442 个文件133.56MB除核心可执行程序外主要包含 html 帮助文档、png/gif 配图、jar 插件、xml/dita 配置及少量 hprof 示例堆转储文件结构接近 Eclipse RCP 标准布局便于按需查阅。已有 365 人学习下载。使用其中的 ParseHeapDump.bat 或 MemoryAnalyzer.exe 加载堆转储可快速生成 Leak Suspects、支配树、直方图与重复对象报告适合 Java 服务端开发者、性能测试人员用于线上内存溢出排查与优化实践。1. 从堆转储到内存真相MAT 是 Eclipse 家族里最值得下载的日志分析工具线上 Java 服务凌晨 OOM留下一份 .hprof 文件和几行 GC 日志翻完日志除了 OutOfMemoryError 堆栈什么线索都找不到这是很多后端开发都经历过的场景。Eclipse Memory AnalyzerMAT就是专为这种场景设计的工具它把堆转储这份最原始的内存日志解析成直方图、支配树和泄漏嫌疑报告让你在半小时内从几个 GB 的二进制文件里找到占内存的具体对象和引用链。无论你是后端、Android 开发者还是运维只要和 JVM 内存问题打交道MAT 都值得下载并放进工具箱。2. 先理解 MAT 怎么工作堆转储、支配树与保留堆2.1 堆转储就是 JVM 的案发现场GC 日志是流水账hprof 才是快照OOM 之后你手上有哪些东西应用日志里的异常堆栈、监控面板上的内存曲线、GC 日志里的回收记录。前两个只能告诉你“哪里爆了”GC 日志告诉你“怎么爆的”但真正要定位“谁占着内存不放”你得有一份堆转储。堆转储是 JVM 在某个时刻对 Java 堆拍的一张全景照片里面记录了所有存活对象、类元数据、字符串常量以及它们之间的引用关系序列化后就是 .hprof 文件。MAT 这个工具解决的核心问题就是把这堆二进制数据重新翻译成一棵能看清引用关系的对象树。这里有个常见误解GC 日志和堆转储都算日志但信息粒度完全不同。GC 日志是流水账一行一行记录每个 GC 事件前后各分区容量变化堆转储是快照把某一瞬间的整个内存世界冻结下来。排查内存泄漏的正确顺序是先看 GC 日志确认老年代持续增长再用堆转储分析是谁在增长。单看 GC 日志你只能确定该抓现场了但抓完现场怎么下手得靠 MAT。这也是为什么很多团队工资最高的那个后端桌面上的常驻工具必然是 MAT 而不是 IDE。为了保证 OOM 时不至于裸奔我一般在所有服务里固定加上这两行 JVM 参数-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/heap-${HOSTNAME}-$(date %Y%m%d%H%M%S).hprof第一行让 JVM 在抛出 OutOfMemoryError 之前先把堆内存导出来第二行指定导出路径文件名里带上主机名和时间戳避免多实例互相覆盖。注意触发时机是“即将抛出 OOM 之前”所以文件里记录的是崩溃前一瞬间的真实内存状态对排查泄漏几乎是最佳证据。这也意味着如果你的服务跑在 systemd 或者容器里务必把 HeapDumpPath 指向持久化磁盘目录否则容器一重启文件就丢了。我见过太多生产事故应用确实自动导出了 hprof但临时目录随容器销毁事后只能靠猜那份后悔药吃得很痛。2.2 浅堆、保留堆与支配树MAT 的三个核心指标打开 MAT 后界面里最常出现的三个词是 Shallow Heap、Retained Heap 和 Dominator Tree。看不懂这三个概念后面所有分析都是在猜。浅堆Shallow Heap指的是对象自身占用的内存不含它引用的其他对象。比如一个 ArrayList 实例浅堆可能只有几十字节但它内部数组引用着成千上万个元素这些元素才是内存大头。保留堆Retained Heap则是“如果这个对象被回收整棵引用链上能释放多少内存”它把该对象直接或间接引用的所有对象都算了进去。所以判断泄漏嫌疑优先看保留堆因为它更接近“真实占用”。举个具体例子一个 HashMap 实例浅堆可能只有 48 字节但它作为缓存持有了一万个订单对象保留堆可能是几十 MB。你在直方图里按浅堆排序前几名全是数组和 String根本看不出问题按保留堆排序HashMap 立刻暴露。这也是为什么我打开 Histogram 的第一件事就是把排序列切成 Retained Heap。支配树Dominator Tree解决的是引用关系太复杂的问题。JVM 里对象互相引用A 引用 BB 又引用 CC 还引用 A直接看引用图会绕晕。MAT 把所有对象按“支配关系”整理成一棵树父节点是“如果自己被回收子节点必然也被回收”的支配者。换句话说支配树里某个大节点被删掉它下面一整枝都能被回收。内存泄漏的源头通常就是支配树第一层里某个保留堆很大的节点沿着它往下看就能看到具体被强引用住的业务对象。这个推导思路是 MAT 所有自动报告的地基。2.3 和同类工具比MAT 的优势不在好看在分析深度很多人在选内存分析工具时会在 VisualVM、JProfiler 和 MAT 之间纠结。我的建议是别只按“能不能看堆”来选要按你手里有没有 hprof 文件来选。工具实时监控CPU 分析堆转储分析授权VisualVM强弱弱随 JDK 分发JProfiler强强中商业授权Eclipse MAT弱无强开源免费VisualVM 是 JVM 监控里的瑞士军刀但它在打开几个 GB 的 hprof 文件时非常吃力引用链分析基本靠肉眼。JProfiler 实时性能分析很强适合做压测和在线 profiling但对离线 dump 的支配树和 OQL 支持反而不如 MAT。MAT 是专注“事后分析”的工具你手里已经有一份 dump你需要最快速度回答“哪些对象吃掉了堆谁在引用它们”。它的自动泄漏嫌疑报告和 OQL 查询在同类工具里做得最透。另外强调一点MAT 是 Eclipse 基金会下的开源项目遵循 EPL 协议商业团队使用没有任何授权成本。有些装机量很大的商业工具需要买 license而 MAT 只要你的机器内存够就能一直白嫖。选择它做团队标准内存分析工具预算上完全没负担。3. 下载与安装从拿到安装包到改对 MemoryAnalyzer.ini3.1 版本选择1.11.0 还是 1.10.0以及三个关键文件MAT 现在有三种获取方式独立版安装包、Eclipse IDE 插件、命令行版。独立版最省心这也是我推荐绝大多数人的方式。搜索关键词 “eclipse mat 1.11.0 win 64 下载” 能找到 64 位 Windows 安装包早期的 1.10.0 也很稳定如果你的 JDK 版本比较老选它更稳妥。下载解压后看一眼目录里这三个东西MemoryAnalyzer.exeWindows 启动器双击直接进入图形界面MemoryAnalyzer.iniMAT 自身 JVM 参数的配置文件重点改这里plugins 目录包含所有分析插件不要随便删。首次打开前我强烈建议先改 MemoryAnalyzer.ini 的 -Xmx。默认值是 1024m分析超过 1GB 的 dump 必卡。-vmargs -Xmx4096m -XX:MaxMetaspaceSize512m -XX:UseG1GC-Xmx 是 MAT 进程能用的最大堆内存一般设为你要分析的 dump 文件大小的 1.5 到 2 倍。如果你要分析 4GB 的 hprof就设 -Xmx6g机器只有 16GB 内存的话同时打开 IDE、浏览器和 MAT 会非常勉强建议关掉几个重量级应用再分析。UseG1GC 是给 MAT 自己的 JVM 选择垃圾回收器处理大堆时 G1 的停顿控制比默认的 Parallel GC 更平滑分析过程不容易出现界面假死。如果想把 MAT 嵌入 Eclipse IDE 使用也可以通过插件市场在线安装。但插件模式共享 Eclipse 的堆内存Eclipse 自身跑 Maven 索引和编译就已经吃很多内存再加载大 dump 非常容易翻车。日常建议用独立版IDE 里那个插件我基本只用来从快速堆栈里点开小文件瞄一眼。3.2 抓取堆转储jmap、jcmd 与自动触发参数分析的前提是有一份合格的 hprof 文件。生产环境最常用的抓取方式是 jmap。假设服务 PID 是 12345jmap -dump:live,formatb,file/data/logs/heap-$(date %Y%m%d%H%M%S).hprof 12345live 参数表示只导出堆中存活对象能显著减小文件体积但代价是漏掉那些已经不可达的对象。如果你怀疑“大量对象创建后没被回收”用带 live 的 dump 没问题如果你怀疑“对象被错误地缓存在静态变量里”这些对象还活着也抓得到。formatb 指定输出二进制格式file 是路径加文件名。不加 live 的完整 dump 包含所有对象信息更全但文件更大、MAT 解析更慢。实际上高版本 JDK 我更推荐用 jcmdjcmd 12345 GC.heap_dump /data/logs/heap-$(date %Y%m%d%H%M%S).hprofjcmd 是 JDK 诊断命令的统一入口它在权限控制和扩展性上比 jmap 更规范而且不强制加 live 标志默认导出全量堆。注意 jmap/jcmd 在执行时会让 JVM 进入安全点Stop The World对大堆的暂停时间可能长达几秒到几十秒。生产环境不要频繁抓取选业务低谷期执行并且确保磁盘剩余空间大于预计 dump 大小。自动触发则回到上一章那两行参数。如果应用还没崩你想看“内存涨但没 OOM”的瞬间就用 jmap/jcmd。如果应用已经 OOM 过HeapDumpOnOutOfMemoryError 的记录已经躺在磁盘里直接拿它分析就行。分析师手头没有 dump 的时候再多理论都是空谈这个黑匣子必须靠工具打开。3.3 打开 dump 与生成 Leak Suspects 报告启动 MAT 后工作台界面和 Eclipse IDE 很像但菜单精简很多。选择 File 菜单下的 Open Heap Dump找到刚才导出的 .hprof 文件点击打开。MAT 会先进入解析阶段状态栏显示进度文件越大耗时越长。解析完成后会弹出向导询问是否生成 Leak Suspects Report。第一次打开建议直接确认让 MAT 自动跑一遍。Leak Suspects Report 是 MAT 最省心的功能它会分析堆里的引用关系把“最可疑的保留堆集合”按照占用从上到下排序每个嫌疑点都附带说明显示这个对象占了多少内存、被什么类型的引用链持有。新手哪怕不懂支配树只看报告首页的嫌疑列表也能把分析范围从几十万对象缩小到两三个类。报告里如果看到类似 “class java.util.HashMap 0x7a3d3a38” 的信息别急着以为是 HashMap 泄漏点进去看它引用的具体业务对象比如订单类、Session 类那才是真正的线索。打开主界面后界面顶部会有一个 Overview 页面显示 JVM 堆大小、类数量和总对象数。再往下是 Actions 区域有 Histogram、Dominator Tree、OQL 等入口。后续所有分析都从这些入口进不用再回到文件菜单。4. 实战定位泄漏从直方图到支配树再到 OQL4.1 先看 Histogram按保留堆排序圈定嫌疑类打开 dump 后通常都会默认停在 Overview我习惯直接点 Actions 里的 Histogram 进入直方图视图。这个视图按类汇总所有实例每一行显示类的对象个数、Shallow Heap 和 Retained Heap并且支持点列头排序。第一步操作是按 Retained Heap 降序排列。排在最上面的类基本能反映业务特征一个订单系统里Order 和 OrderItem 排在前列是正常的但如果你看到 ThreadLocal 或者 java.lang.Thread 出现在头部就要警惕了。线程对象本身占用不大但它作为 GC Root 会间接持有整个线程栈里的对象保留堆被拉高通常意味着线程数量异常或者线程内缓存了不该缓存的大对象。第二步是对某个类右键选择 List Objects 下的 with outgoing references可以查看这个类的对象引用了谁选择 with incoming references则能查看到底是谁在引用这个类。排查内存泄漏核心就是沿着 incoming references 从业务对象一路往 GC Roots 找找到那个本该被回收却一直被持有的引用链。注意直方图里默认显示“按类汇总”如果你想看某个类的指定实例双击类名展开就会列出每个实例的单体占用还能按保留堆排序。这里如果发现同一类有几百个近乎相同的大对象那基本就是循环里重复创建并缓存导致的。直方图顶部还有一个过滤框支持输入类名关键字实时过滤。比如想看所有 Session 相关对象敲 Session 回车列表立刻收窄。大堆里类名成千上万过滤框能省很多时间。我一般先过滤业务关键类再结合保留堆排序把非业务类排到脑后。4.2 用支配树找引用链只留最粗的路径直方图能告诉你“哪个类占了大头”但回答不了“为什么它没被回收”。这时候切到 Dominator Tree 视图。操作方式是右键直方图里的嫌疑类选择 Show Objects by Class然后切换到 Dominator Tree 分页。这个视图会把对象按支配关系铺成一棵树父节点是支配者子节点是被持有对象。树的第一层往往是一些集合类、缓存类或者线程对象。比如一个缓存 Map 的 value 是强引用支配树里这个 Map 会非常大它的 value 对象全部成为树上直接子节点。看到这种结构基本可以断定缓存没有做容量控制或者 value 不该用强引用。下一步在某个实例上右键选择 Path To GC Roots再选 Show All PathsMAT 会列出从该对象到所有 GC Root 的完整引用路径。这些路径里如果出现 java.lang.ref.Finalizer 或者 Socket 这种类往往就是“资源没关导致对象被钉住”的典型线索。我常用的操作是先在支配树里选中几个大头节点右键导出路径列表然后把重复出现的业务类标出来。多个嫌疑对象指向同一个业务类时不要只看单个实例要向上看它们共同的支配者。这一步非常依赖对业务代码的记忆所以分析前最好让负责对应模块的同事一起盯屏幕毕竟工具只能给出静态引用业务关系还得人肉确认。4.3 OQL 精准过滤把可疑对象从海量实例里捞出来MAT 内置了类 SQL 的 OQL 查询语言适合在几十万实例里做精准筛选。点工具栏的 OQL 按钮就能打开查询编辑器。比如我怀疑系统里有大量未关闭的订单 SessionSELECT * FROM com.example.Order o WHERE o.status CREATED AND o.createTime 20230101这个查询会筛出所有 status 为 CREATED 且 createTime 早于 2023 年 1 月 1 日的订单对象。注意 MAT 的 OQL 语法和标准 SQL 不一样表名必须是类的全限定名字段访问直接点属性名字符串比较用单引号。它不支持 JOIN也不支持 GROUP BY适合做条件过滤不适合做聚合统计。另一个高频用法是查超大集合SELECT * FROM java.util.ArrayList l WHERE l.size 10000执行后会把所有容量超过一万的 ArrayList 实例列出来右键直接看 incoming references顺着引用链就能找到是哪个业务模块把这个集合养大的。如果查询结果为空说明问题不在集合容量而在对象数量那回头去直方图按类看实例数更合适。OQL 的完整语法在 MAT 安装目录的 help 里能找到模板平时用到最多的是 SELECT、WHERE 和字段条件组合记住这三样就能覆盖 90% 的过滤场景。5. 避坑指南Eclipse MAT 分析中最容易翻车的五个场景5.1 现象搜“eclipse mat”却下载到了 MATLAB或者反过来用 MAT 打开 .mat 文件报 invalid format原因MAT 和 MATLAB 的缩写撞车了。Eclipse MAT 的全称是 Memory Analyzer处理 Java 堆转储MATLAB 则用 .mat 保存矩阵数据两者毫无关系。搜索关键词 “matlab怎么打开mat文件” 找到的是后者的帮助文档而搜索 “eclipse mat下载” 才进入 Eclipse Memory Analyzer 页面。很多人下载前没仔细看软件名安装完才发现是个几 GB 的数学计算平台。解决下载前核对两点。第一程序名必须是 Memory Analyzer启动文件是 MemoryAnalyzer.exe 或 MemoryAnalyzer.app第二MAT 能打开的文件后缀是 .hprof、.dump 和部分自定义二进制格式无论如何都不是 .mat。如果你手里的诊断包确实是 .mat那就去 MATLAB 的文档里找 load 函数MAT 帮不了你。把这两个工具从脑子里分清楚能少走两小时弯路。5.2 现象Eclipse 里报 An internal error occurred during: Updating Maven Project然后 MAT 插件打不开原因这个报错和 MAT 基本没有直接关系。它发生在 Eclipse IDE 的 Maven 插件自动更新工程时常见诱因是网络代理配置错误、本地 .m2 仓库里的依赖文件损坏或下载不完整以及工作空间索引冲突。很多人以为是 MAT 插件占内存导致的问题于是删除重装 MAT重启后报错依旧白白浪费时间。解决先别动 MAT。打开 Eclipse 的错误日志视图搜索 “Updating Maven Project” 后面的 Caused by 堆栈。如果是 SocketTimeoutException 或 Connection timed out检查 Maven 的 settings.xml 镜像配置如果是一堆 .lastUpdated 文件进入本地仓库目录把它们删掉再用 Maven 的 -U 参数强制执行更新。实在不行就新建一个工作空间重新导入项目。这期间 MAT 插件一直好好的问题解决后自然就能打开。5.3 现象MAT 打开大 dump 时直接卡死或者提示 OutOfMemoryError原因这是最典型的 MemoryAnalyzer.ini 没配置到位。MAT 默认 -Xmx1024m一个 2GB 的 hprof 文件在解析时会产生比原文件更大的临时模型1GB 堆根本扛不住。卡死不是 dump 文件损坏而是 MAT 自身的内存不够很多新手在这里误删 dump结果损失了唯一的排查证据。解决打开安装目录下的 MemoryAnalyzer.ini把 -Xmx 设为 dump 文件大小的 1.5 到 2 倍。比如分析 4GB dump设 -Xmx6g。如果物理内存只有 8GB建议先用 jmap -dump:live 抓一个小一点的 dump或者用后面要讲的命令行模式分批导出报告不要强行一次性加载全量数据。另外别忘了给 MAT 单独的 JDK 环境不要和 IDE 共用 JAVA_HOME避免版本冲突。5.4 现象macOS 上双击 MAT.app 没反应或者提示应用已损坏原因MAT 没有做完整的应用签名和公证macOS 的 Gatekeeper 会拦截来自未认证开发者的应用。另一个常见原因是下载的压缩包在解压时被标记了隔离属性导致系统认定文件不可信。解决不要急着重新下载先移除隔离属性。xattr -dr com.apple.quarantine /Applications/MemoryAnalyzer.app执行后再打开。如果还不行去系统设置里的隐私与安全性在“仍要打开”区域放行 MemoryAnalyzer。这个操作在 Intel 和 Apple Silicon 上都适用但要注意下载时选择对应架构的版本否则启动器可能直接崩溃。5.5 现象Android 开发的 hprof 导入 MAT 后全是乱码和 unknown 类名原因Android 的 Dalvik/ART 虚拟机导出的 hprof 不是标准 JVM 格式需要先用 Android SDK 里的 hprof-conv 工具转换MAT 才能正确识别类名。很多人拿 Android Studio 的 Dump Java Heap 功能抓出来的文件直接拖进 MAT看到的全是类似 class java.lang.String (0x…) 的解析失败信息就以为是 MAT 版本太老。解决转换一次再导入。hprof-conv -z input.hprof output.hprof-z 参数用于压缩基本类型数组转换后的文件才能被 MAT 正常解析。更省事的方式是直接用 Android Studio 的 Profiler 导出 hprof 时勾选“转换为 MAT 可用格式”的选项让 IDE 替你完成转换。转换完成后再打开 MAT 就能看到 Activity、Fragment 这些真实类名继续按直方图和支配树的思路分析。6. 多学一招用命令行模式让 MAT 分析自动化结果直接落盘MAT 不只有图形界面它自带命令行批处理模式。线上服务器通常没有显示器而 GUI 分析又依赖本地大内存如果你只是需要快速确认泄漏方向完全可以在服务器上跑 MAT 命令把报告导出成文本再慢慢看。命令行用法是在 MAT 安装目录下执行 ParseHeapDump 脚本。Windows 是 ParseHeapDump.batmacOS 和 Linux 是 ParseHeapDump.sh基本格式如下./ParseHeapDump.sh /data/logs/heap.hprof \ org.eclipse.mat.api:acquire,org.eclipse.mat.api:leak_suspects,org.eclipse.mat.api:top_components逗号分隔的每个参数对应一种分析任务。acquire 生成堆转储摘要leak_suspects 生成泄漏嫌疑报告top_components 生成按保留堆排序的大对象清单。执行完成后同目录下会出现一个 .zip 压缩包里面是 HTML 格式的报告可以直接打开或者归档。图形界面里看到的 Leak Suspects 报告本质上就是同一套解析 API 的产物所以命令行生成的结果和 GUI 完全一致不存在“命令行不准”的问题。还有一个技巧把这条命令封装成一个带日期的脚本放进每次事故处理的固定动作里。无论团队其他成员用什么方式打开 dump都直接看这份落盘报告避免各人电脑上重复解析耗时且版本不一致。以前我拿到一份 hprof 习惯性先双击打开 GUI结果当天晚上分析完没存档第二天想再看细节只能重新加载整个文件白白等了半个多小时。从那以后我每次收到转储文件都会强制走一遍命令行导出报告再决定要不要进 GUI 做深入分析。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表