资讯动态

MemoryAnalyzer实战:JDK8堆转储分析与内存泄漏定位

发布时间:2026/10/9 13:10:27 来源:尧图企业网站定制
简介面向 Java 开发与性能调优人员这份 Memory Analyzer ToolMAT1.11.0 Windows x86_64 版本资源包可在 JDK8 环境下作为 Eclipse 插件或独立工具使用用于堆转储dump文件分析、内存泄漏排查与 OOM 问题定位。包内共有 453 个文件压缩包大小约 76.24MB主体是 183 个 jar 程序与插件另有 html/pdf 帮助文档、xml/css 界面配置、properties/ini/prefs 运行与环境参数、mf/sf/rsa 签名校验文件以及 exe/bat/dll 等启动运行文件png/gif 图标等界面素材也一应俱全目录遵循 Eclipse RCP 典型布局。已有 2496 人学习下载适合需要深入分析与排查 Java 堆内存的工程师。借助该工具可查看堆中对象占用大小、实例数量与引用关系通过 OQL 对象查询快速筛选可疑数据还可结合直方图、支配树、线程栈与 GC Roots 引用路径定位泄漏源头并生成内存泄漏 suspect 分析报告为 OOM 及性能优化提供清晰依据。资源包结构完整解压后即可配置 JDK8 环境启动适合作为 Java 工程师排查线上内存溢出的常备工具。1. MemoryAnalyzer 这个包是做什么的JDK8 堆转储分析的起点拿到 MemoryAnalyzer(JDK8)-1.11.0.20201202-win32.win32.x86_64.zip 这样的包第一反应往往是找不到安装向导。其实它是个免安装工具解压就能用——也就是做内存分析时常说的 MAT专门解析 Java 进程的堆转储文件hprof回答三个问题谁占着内存、谁在引用它、泄漏点在哪。版本串里 1.11.0.20201202 是 2020 年 12 月发布的 JDK8 适配构建win32 是平台代号x86_64 才是真实架构即 64 位 Windows。它自带 Java 8 运行时对还在维护 JDK8 的老项目尤其友好。下文面向一线 Java 服务排查者按实际干活顺序讲装起来、抓堆转储、定位泄漏再把生产环境常见的坑和验证方法说透。2. 在 Windows 上跑起 MemoryAnalyzer解压、启动参数与首次内存配置这一章解决“怎么把它变成能双击运行的工具”。常见做法是把 zip 解压到一个纯英文路径下改一改启动参数再导入一个 hprof 验证整条链路。这里的所有命令都按 Windows 环境写路径里的盘符和目录按你自己的机器替换。2.1 目录结构先认清哪些文件是入口、哪些不能动这个发行版解压后是自包含目录不存在安装到系统的问题也不会往注册表写东西。解压后的核心结构大致如下mat/ ├── MemoryAnalyzer.exe # 图形界面启动入口 ├── MemoryAnalyzer.ini # 启动参数调内存主要改这个 ├── jre/ # 自带 Java 8 运行时 ├── plugins/ # 插件目录hprof 解析器和 OQL 引擎都在这里 └── configuration/ # 工作区配置缓存和锁文件在这里这里有两个经验。第一目录路径不要带中文和空格。虽然工具本身能处理空格但后面抓取 hprof 时HeapDumpPath、命令行脚本、批处理拼接都会因为路径多一层引号而容易出错。我习惯统一放到 C:\tools\ 或 D:\devtools\ 这种纯英文路径。第二MemoryAnalyzer.exe 是图形入口但 MAT 也支持命令行模式可以在无界面环境下导出 Leak Suspects 报告第 6 章会提到先不展开。解压这一步Windows 上有两种常用命令。PowerShell 的 Expand-Archive 适合手动操作tar 适合写进脚本因为它能直接指定解压目标目录# PowerShell 解压-Force 表示目标目录存在时也继续 Expand-Archive -Path D:\downloads\MemoryAnalyzer(JDK8)-1.11.0.20201202-win32.win32.x86_64.zip -DestinationPath D:\devtools\mat -Force # 或者用较新版本 Windows 自带的 tar cd D:\downloads tar -xf MemoryAnalyzer(JDK8)-1.11.0.20201202-win32.win32.x86_64.zip -C D:\devtools\mat参数说明Expand-Archive 的 -DestinationPath 是目标目录-Force 允许覆盖已存在的文件tar 的 -C 指定解压目标目录使用前先确认目标目录存在否则会直接报路径错误。两个命令效果一样按环境选一个就行。解压完检查一件事MemoryAnalyzer.exe 是否在根目录下。个别历史构建会把可执行文件放到子目录里如果遇到嵌套目录把整个子目录内容移动到根目录即可不影响功能。下载和安装前还有一个常见误区要澄清包名里的 win32 不代表 32 位程序。Eclipse 系的平台命名里win32 是指 Windows 图形环境家族真实位数看 x86_64 三个字段。所以这个包只能在 64 位 Windows 上运行32 位 JVM 支撑不起大堆分析后面第 5 章会专门说这事。2.2 改 MemoryAnalyzer.ini把 MAT 自己的堆内存调大MAT 本身是个 Java 程序解析几个 GB 的 hprof 时默认的 1GB 堆会直接报 Java heap space。这大概是所有用户遇到的第一个坑。打开 mat 目录下的 MemoryAnalyzer.ini找到带 -vmargs 的段落把堆调大-vmargs -Xmx4096m -XX:UseG1GC参数说明-Xmx 是 MAT 进程自己的最大堆不是解析目标 dump 的堆两者没有必然关系。给多大没有标准答案我按 hprof 文件大小来定dump 文件 2GB 以下给 34GB2GB 到 5GB 给 68GB更大就考虑命令行导出报告避免图形界面长时间卡住。物理内存不够时宁可不解析也不要把系统 swap 打满否则连操作系统都会变慢。加了 -XX:UseG1GC 是因为几 GB 堆下 G1 的停顿更平稳机器内存紧张时可以删掉这行让 MAT 用 JDK8 默认的 ParallelGC。修改 ini 时有个容易翻车的细节这个文件是 UTF-8 编码不要用记事本另存为带 BOM 的 UTF-8。启动器读 ini 时认不出 BOM结果就是双击什么都没发生。JDK8 这个发行版自带 jre一般不用手写 -vm 指向运行时但如果双击后提示找不到 Java可以在 -vmargs 之前单独写一行-vm下一行写 jre 里 jvm.dll 的绝对路径。改完先别急着打开大文件先用下一节的最小流程验证启动。2.3 首次启动导入一个 hprof 看 Overview验证安装最直接的办法是拿一个现成的 hprof 文件打开。没有现成 dump 的话用第 3 章的方法抓一个小文件就行。假设你已经有 app.hprof启动命令很简单cd /d D:\devtools\mat start MemoryAnalyzer.exe启动后的操作路径File → Open Heap Dump → 选择 app.hprof。首次打开时MAT 会弹窗询问是否生成 Leak Suspects 报告这一步包含完整的可达性分析最耗时、最吃内存。几百 MB 的小 dump 直接点确定大 dump 建议先选 No进去后用 Histogram 粗看再决定要不要完整跑。文件打开后落在 Overview 页面能看到 Total Heap、Classes、Class Loaders 等信息右上角 Actions 面板里 Leak Suspects、Histogram、Dominator Tree 三个入口都在这。很多第一次用的人会在这一步遇到“An internal error occurred during: Parsing heap dump”第一反应是文件损坏。实际上只要 hprof 是用 formatb 抓的绝大多数情况是 MAT 自己的堆不够回 2.2 节把 -Xmx 调大再开就行。这个现象太典型了第 5 章我会再展开讲一遍现象和排查顺序。3. 用 JDK8 自带工具抓堆转储jmap、jcmd 与 OOM 自动转储MAT 再好没有合格的 hprof 也是巧妇难为无米之炊。这一章讲怎么从 JDK8 进程里拿到堆转储。抓法主要有三条路jmap 手动抓、jcmd 手动抓、以及提前在 JVM 参数里埋好 OOM 自动转储。三者的选择取决于场景能复现的线下问题用 jmap线上突发的 OOM 必须靠自动转储。3.1 手动抓取jmap 是最直接的路但 live 参数要慎用在 JDK8 里jmap 是诊断堆的标准工具。第一步先找到目标进程号这一步经常有人卡住因为 Windows 上 64 位 JDK 自带的 jps 能列出的进程范围受当前用户权限影响最好用同一个系统账号执行# 列出当前机器的 Java 进程 jps -l # 查看某个进程的堆使用概况 jmap -heap 12345jmap -heap 的输出里有 Eden、Survivor、老年代、Metaspace 的容量和使用量。抓 dump 之前先看一眼老年代使用率如果老年代已经 90% 以上说明进程正处在危险水位抓取动作本身可能成为压垮它的最后一根稻草。确认进程号后抓堆转储# 抓完整堆转储不触发 Full GC包含不可达对象 jmap -dump:formatb,fileD:\dumps\app-full.hprof 12345 # 只抓存活对象会先触发 Full GC线上禁用 jmap -dump:live,formatb,fileD:\dumps\app-live.hprof 12345参数说明formatb 表示二进制 hprof 格式MAT 只认这种file 指定输出路径目录必须存在。带 live 时 jmap 会先做一次 Full GC只保留可达对象文件更小但代价是线上服务明显停顿。老年代接近满的时候这次 Full GC 可能直接把服务拖进 OOM我见过不止一次因为图省磁盘而把服务搞挂的事故。所以我的默认选择是线上用不带 live 的完整 dump线下复现时才考虑 live。另外jmap 抓取大堆4GB 以上时耗时可能以分钟计因为 JVM 要暂停应用线程做堆遍历。抓之前告诉团队“接下来可能有几分钟 GC 停顿”抓完立刻用 jmap -heap 对比前后内存水位确认 dump 时间点有没有代表性。3.2 一键配置让 OOM 自动吐出 hprof手动抓包最怕错过现场。生产环境的常见做法是提前在 JVM 参数里埋开关发生 OutOfMemoryError 时 JVM 自动把当时的堆状态落盘这是性价比最高的内存巡检手段java -Xms2g -Xmx2g \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPathD:\dumps\app-oom.hprof \ -jar app.jar参数说明-XX:HeapDumpOnOutOfMemoryError 是开关开启后 OOM 自动写 hprof-XX:HeapDumpPath 指定写入路径。这里有两个高频坑一是路径如果只到目录级比如 D:\dumps\JDK8 会自己生成 java_pid12345.hprof 这样的文件名二是目录必须提前存在且对运行账号有写权限否则 OOM 现场直接丢失日志里只有一行“Failed to write heap dump”。我习惯固定写完整文件路径并在启动脚本里用带时间戳的变量动态生成避免旧现场被覆盖。抓完之后的纪律同样重要先把 hprof 复制一份归档再用原件去 MAT 分析。因为 MAT 解析时会在文件同目录生成 .index、.domTree 等索引文件这些辅助文件体积不小反复在原件上打开大 dump 还会让缓存状态混乱影响结果一致性。归档目录里只留原始 hprof索引文件随时可以重新生成。3.3 jcmd 与 jmap 的差别什么时候用哪个JDK8 里还有一条更正规的路——jcmd。它和 jmap 底层走同一套 attach 机制但命令语义更清晰后续版本的 JDK 也在向 jcmd 收敛# 用 jcmd 抓堆转储-all 表示抓全部对象包含不可达对象 jcmd 12345 GC.heap_dump -all D:\dumps\app-jcmd.hprof这里有个关键区别要注意jcmd 的 GC.heap_dump 不带 -all 时行为类似 jmap -dump:live会先触发 Full GC 且只留存活对象显式加上 -all 才等价于 jmap 默认的完整 dump。所以我给团队的统一约定是用 jcmd 时必须带 -all除非你明确知道自己要的就是 live 语义。jmap 这边反而默认就是全量 dumplive 是选装项两个工具的默认行为不一致这是最容易记混的地方。我的建议是 JDK8 环境两把工具都留着jmap 看堆概况和快速抓取jcmd 用于脚本化统一入口。容器环境里还要注意一点如果镜像是裁剪过的 JREjmap/jcmd 可能不存在或者 attach 权限被容器安全策略挡住报 “Unable to open socket file”。遇到这种问题不要和权限死磕更干净的办法是让基础镜像带完整 JDK诊断工具缺失本身就是隐患。4. 从堆转储定位内存泄漏Leak Suspects、Dominator Tree 与 OQL有了 hprof接下来才是 MAT 的主场。这一章按实际排查顺序走先看自动生成的嫌疑报告缩小范围再用直方图和支配树人工验证引用链最后用 OQL 做针对性查询。四步走下来大部分内存问题都能在半小时内给出结论。4.1 打开 dump 后先看什么Overview 与 Leak Suspects第一次打开 hprof 时弹窗询问是否生成 Leak Suspects 报告这是 MAT 最值钱的功能。它会自动做一轮可达性分析把堆里最像泄漏点的对象按嫌疑排序每个嫌疑点给出三块关键材料Shortest Paths To the Accumulation Point 展示从 GC Roots 到累积点的最短引用链Accumulated Objects in Dominator Tree 量化这个点支配了多少对象和内存底部的 Details 列出嫌疑类分布。注意Leak Suspects 是自动生成的嫌疑名单不是判决书。结论一定要回到 Dominator Tree 或引用链上人工确认否则容易被误导。我的经验是它能把排查范围从“整个堆”缩小到“一两条引用链”效率提升非常明显。但生成报告的计算量大大堆超过 4GB首次运行可能耗时几分钟而且要求 MAT 的 -Xmx 足够。如果内存不够跑完整报告就先在 Overview 点 Histogram手工筛 Retained Heap 大的类再对单类看引用链效果一样只是路径长一点。4.2 HistogramShallow Heap 和 Retained Heap 哪个才是关键Histogram 按类统计实例数量、Shallow Heap、Retained Heap。对排查来说排序应该按 Retained Heap而不是按实例数或 Shallow Heap。两张表的含义差别很大列名含义排查作用Class Name类名快速看哪些业务类实例爆炸Objects实例数量数量异常增长说明对象在堆积Shallow Heap对象自身字段占用的堆不含引用单个对象通常很小不是判断泄漏的主要依据Retained Heap对象连同它强引用的整棵子树大小决定“删掉它能回收多少”这里有个新手必踩的坑盯着 Shallow Heap 看看到的 Top 类永远是 char[]、byte[]、String误以为字符串是泄漏元凶。其实这些只是被业务对象在背后撑着的“重量”真正的持有者在更上层。排查思路应该是先用 Retained Heap 找最重的类再沿着引用链往上看谁持有它。另外Histogram 默认 Retained Heap 是 0 或未计算需要右键选 Calculate Retained Sizes 才出真实值大堆下这一步也吃内存可以对单个类单独计算不必全量跑。4.3 Dominator Tree找到真正“持有”泄漏对象的人Histogram 告诉你哪类对象多Dominator Tree 告诉你这些对象受谁支配。支配关系的通俗解释如果删除对象 A 后B 也变得不可达那么 A 支配 B。在 MAT 中进入 Dominator Tree找到 Retained Heap 大的节点右键选 Path To GC Roots → with all references就能看到从 GC Roots 下来的完整链路典型形态如下层级对象说明GC Rootsmain 线程主线程栈帧持有静态字段静态字段com.example.AppMain.holder泄漏对象的根持有者容器java.util.HashMap缓存容器本身膨胀业务对象demo.model.Order大量实例堆积Retained Heap 最大这个视图是验证代码逻辑猜想的最终手段。实际排查里我一般先看 Retained Heap 最大的节点展开它的子树构成通常两三步就能锁定是某个缓存 Map、某个线程栈帧还是某个内部类。注意 Path To GC Roots 的过滤菜单里有 Exclude weak/soft references 选项。如果挂住对象的是 WeakReference通常不算泄漏强引用链才是问题本体优先查强引用。4.4 OQL像查数据库一样查堆图形界面在超大堆面前会卡OQL 是最后一公里的杀手锏。OQL 语法接近 SQL但对象模型是 Java 类可以直接访问类的字段。几个高频查询-- 查所有内容超长的字符串按文本找疑似 key SELECT * FROM java.lang.String s WHERE s.value.length 10000; -- 统计某个业务类的实例数量 SELECT COUNT(*) FROM INSTANCEOF com.example.model.Order; -- 枚举某个缓存实现类的全部实例 SELECT * FROM com.example.CacheImpl c;逻辑说明第一条里的 value 是 String 内部的 char[] 字段OQL 可直接点字段链用来找超长字符串非常快第二条的 INSTANCEOF 表示包含所有子类比直接写类名更全第三条返回该类的实例列表方便继续右键转成 Histogram。执行完结果集后右键可以再发给 Histogram 或 Dominator Tree等于把查询结果继续喂给图形分析。容易踩的是类名写法内部类和数组要按 JVM 内部名来写比如 java.util.HashMap$Node、[Ljava.lang.String;少写一个 $ 或分号就会报解析错误。4.5 一个完整的缩小范围案例模拟项目X的内存问题把上面这些串起来看一个模拟项目X的例子某服务上线后老年代每两小时涨 300MBFull GC 后回落不明显。先 jmap 抓完整 dumpMAT 打开后跑 Leak Suspects报告指向一个叫 sessionCache 的 HashMap。点开 Shortest Paths引用链是 main 线程 → 静态字段 sessionCache → HashMap → Node → HttpSession 实现类。回 Histogram 查 session 类实例数发现数量与在线用户数吻合但每个 value 都挂着一整套用户订单列表。结论是缓存把 session 永久持有且没有过期清理。修复后重新压测老年代增长曲线恢复平稳。整个过程从拿到 dump 到定位不到半小时工具的价值体现在把混乱的堆信息收敛成一条可读的引用链。5. 常见问题与排查这些坑我基本都踩过工具本身足够稳但人在解压、配置、抓包时翻车的频率很高。这一章把高频问题按“现象 → 原因 → 解决”写清楚每一条都是我自己或身边人真实遇到过的。5.1 双击 MemoryAnalyzer.exe 没反应也没有报错窗口现象图标闪一下就消失事件日志里看不到任何 Java 异常。原因八成是 MemoryAnalyzer.ini 被改坏了比如保存成了带 BOM 的 UTF-8或者 -Xmx 后跟了非法单位少部分是系统 PATH 上的 Java 与自带 JRE 冲突。解决先用命令行启动看真实报错执行MemoryAnalyzer.exe -consolelog启动日志会直接打出来确认 ini 的 -vmargs 段落干净最后可以删掉 configuration 目录下的 .metadata 缓存重试删除前先备份避免丢失工作区设置。5.2 打开大 hprof 报 Java heap space / Parsing heap dump 失败现象进度条走到一半弹 “An internal error occurred during: Parsing heap dump”。原因MAT 自身堆不够解析阶段就需要把对象索引放进内存。解决按第 2.2 节调大 -Xmx估法很简单MAT 堆给到 hprof 大小的 1.52 倍8GB 以上的 dump 建议命令行导出报告不要硬开图形界面。另外确认用的是 x86_64 包win32 只是平台代号真 32 位 JVM 最大堆只有 1.5GB 左右大 dump 永远解不开。5.3 线上用 jmap -dump:live 抓完服务 Full GC 卡了几秒现象dump 生成了但服务抓取期间停顿明显甚至触发报警。原因live 参数强制 Full GC。解决线上改用不带 live 的完整 dump要减小文件就到 MAT 里过滤或者选择低峰期先手动 Full GC 再抓。这是我最深的血泪教训不要在高峰期为了省几百 MB 磁盘加 live服务停顿造成的损失远超那点存储成本。5.4 配置了 HeapDumpPathOOM 后却没有 hprof 文件现象日志里有 OutOfMemoryError目录里却什么都没有。原因HeemDumpPath 指向的目录不存在、账号无写权限或路径含中文和空格导致 JVM 解析异常。解决统一用纯英文绝对路径启动前用脚本确保目录存在。验证参数是否被 JVM 接受可以执行下面命令# -version 不会触发 OOM只验证参数本身能被 JVM 接受 java -Xmx64m -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPathD:\dumps\verify.hprof -version真正完整验证自动转储要跑一段会撑爆堆的代码第 6 章会给一个可直接用的 Demo。如果 verify.hprof 能正常生成说明参数和目录链路没问题剩下的就交给业务触发 OOM 时的现场。5.5 拿到别人给的 hprof打开全是 char[] 和 String业务类一个不见现象Histogram 里 Top 10 全是 JDK 内部的数组和字符串找不到业务类。原因要么文件根本不是堆转储而是线程栈或文本日志要么是 live dump业务对象已被回收。解决先验证文件头hprof 的二进制头固定以 “JAVA PROFILE” 开头# 看二进制文件头确认是不是标准 hprof head -c 20 app.hprof | xxd如果前 20 字节不是 JAVA PROFILE 开头这份文件 MAT 解析不出有价值的东西。是 live dump 的话重新抓完整 dump并确认抓取时内存水位本身处于高位否则抓到的是“健康状态的堆”分析意义有限。5.6 Retained Heap 算出来全是 0或者结果和预期差很远现象Histogram 右键 Calculate Retained Sizes 后目标类的 Retained Heap 仍然为 0。原因没有真正执行计算或者类被 GC Root 以弱引用方式持有弱引用在可达性分析中不计入保留大小。解决对目标类单独右键计算一次查询引用链时先排除弱引用和软引用再判断强引用链路。泄漏一般只与强引用相关把弱引用混进来会严重干扰判断。6. 验证与进阶把 MAT 用成日常排查习惯6.1 用 5 分钟造一个泄漏现场验证整条工具链怎么证明这套工具链在你自己环境里可用最可靠的办法是本地跑一个确定泄漏的程序让 OOM 自动出 dump再用 MAT 定位到罪魁祸首。下面这段代码故意在静态 List 里不断添加字符串是最典型的“被静态字段强引用”泄漏形态import java.util.ArrayList; import java.util.List; import java.util.UUID; public class LeakDemo { // 静态变量被主线程栈帧强引用典型的泄漏载体 static final ListString holder new ArrayList(); public static void main(String[] args) throws InterruptedException { while (true) { for (int i 0; i 10000; i) { holder.add(new String(UUID.randomUUID().toString().replace(-, ))); } System.out.println(holder.size holder.size()); Thread.sleep(50); } } }编译运行javac LeakDemo.java java -Xmx256m -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPathD:\dumps\leak-demo.hprof LeakDemo等它自然 OOM 后leak-demo.hprof 就会落盘用 MAT 打开并生成 Leak Suspects。正常结果会指向 ArrayList 或 char[]Shortest Paths 里看到 main 线程 → LeakDemo.holder → ArrayList 的完整链路说明自动转储、MAT 解析、引用链定位三段全部打通。这个 Demo 也是我做团队内部分享时的标准验证用例。6.2 进阶技巧用两个 dump 对比定位阶段性增长定位慢泄漏时单张快照不够要在不同时间点各抓一次。我的常用做法是 t1 时刻抓 baseline服务继续跑 4 到 8 小时后抓 t2分别打开后进入各自的 Histogram从一个 Histogram 的工具栏点 Compare选择另一份做增量对比。结果按 Objects 增量和 Retained Heap 增量排序增长最明显的类通常就是泄漏候选。两个 dump 之间最好不要有发版变动否则类加载器变化会让对比失真。除此之外我习惯把调好的 MemoryAnalyzer.ini 复制到团队巡检工具箱里新机器直接用不再现场调参数需要定期分析的场景可以探索 MAT 的命令行批处理导出功能生成报告后人手一份比人人开图形界面高效得多。6.3 收尾用了几年 MAT我最大的教训是内存问题不是打开 dump 就有答案的黑匣子要先确认抓包时机和配置没有坑再看 Leak Suspects 缩小范围最后用 Dominator Tree 和 OQL 验证引用链。1.11.0 这个 JDK8 适配版本到现在仍是老项目排查最顺手的组合它的价值不在于功能多新而在于和 JDK8 的 hprof 格式配合得最稳。现在每次上线前把 HeapDumpOnOutOfMemoryError 配好、把 MAT 的 ini 调好已经成为我的固定动作。这些准备在真正 OOM 时能省下按小时计的时间比任何临时救火都值得。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑