PMAT实战:用heapdump与javacore定位Java内存泄漏

发布时间:2026/10/27 13:04:18
PMAT实战:用heapdump与javacore定位Java内存泄漏 简介IBM Pattern Modeling and Analysis Tool for Java简称GA是一款面向Java开发者与系统管理员的GC日志分析工具用于解读JVM垃圾回收行为、定位长时间停顿与内存分配异常进而辅助调优JVM参数。压缩包共17个文件、约7.9MB主体为可执行JAR文件ga441.jar此外还包含readme.zip使用说明、license许可协议以及中、英、日、韩、德、法、意、葡、西、波、土、捷等多语言界面的html帮助页面便于在不同语言环境下查阅操作指引与注意事项。已有309人浏览学习。借助它对串行、并行、CMS、G1等不同收集策略的定制化解析与可视化报告读者可快速掌握GC日志的判读方法识别频繁回收、内存泄漏等典型瓶颈并把分析结论落到实际参数调整上适合需要排查Java应用性能问题、建立GC调优思路的中高级开发者参考使用。1. 从一份 6 GB 的堆转储说起PMAT 能回答什么、不能回答什么线上服务跑了一天堆占用从 2 GB 爬到 6 GB重启能压回去第二天照涨。这种曲线我见过太多次最后能一锤定音的往往不是监控大盘而是一份 Java 堆转储。PMATPattern Modeling and Analysis Tool for Java就是拆这类文件的离线分析工具给它一份 heapdump 和一份 javacore它给出泄漏疑点列表、对象分布与保留大小、线程栈和锁等待关系把「谁在涨」从猜测变成可核对的数字。它最大的价值在「离线」两个字。生产环境不敢长时间挂 profiler但一份已经落盘的转储你可以在自己机器上翻来覆去看一整晚不影响任何人。它适合三类人正在处理内存告警的后端工程师、要给出根因结论的中间件支持人员、以及需要复现偶发 OOM 的测试同学。反过来它不擅长回答「这个接口为什么慢 30 ms」这类 CPU 侧问题那得换采样工具。下面按我实际拆包的顺序写先确认输入是什么、怎么采再走一遍安装和第一次分析然后用查询和差分把嫌疑对象确定下来最后把踩过的坑列清楚。2. 三种转储文件的分工javacore、heapdump、verbosegc 怎么配齐很多人第一次用这类工具翻车不是因为不会点按钮而是因为手上只有一份文件问题根本不在它覆盖的范围内。动手之前先搞清楚每种文件能回答什么问题比装工具重要得多。2.1 javacore、heapdump、verbosegc 各自回答什么我把三者的关系理解成「现场照片、账本、流水」javacore 是某一瞬间所有线程在干什么的照片heapdump 是那一瞬间堆里每个对象被谁持有的账本verbosegc 是内存随时间变化的流水。文件类型典型格式能回答的问题不能回答的问题javacore / thread dump纯文本 .txt线程卡在哪、谁在等锁、有没有死锁、线程池是否打满哪些对象占内存、谁持有它们heapdump二进制 .phd / .hprof哪个类实例最多、谁保留了最多内存、对象之间的引用链对象是什么时候涨起来的、线程当时在跑什么verbosegc 日志文本 .log每次 GC 前后堆占用、停顿时间分布、老年代增长斜率具体是哪个业务对象把老年代撑满所以单靠一份 dump 说「找到了内存泄漏」说服力是不够的。常见做法是把 heapdump 的「谁」和 verbosegc 的「什么时候」拼起来verbosegc 告诉你老年代在每天 10 点后开始单调上涨heapdump 告诉你上涨的那部分里 70% 是被某个缓存类的 Map 持有两条线一对上结论才站得住。提示PMAT 的主战场是 javacore 和 heapdumpverbosegc 曲线它不做精细绘制我一般用专门的 GC 可视化工具看曲线再把关键时刻的堆转储丢进 PMAT 看内容两个窗口并排放。2.2 在生产上把文件采下来不重启服务这里有个必须提前知道的事实堆转储不是想采就能采的。很多 JVM 实现需要你在启动参数里先把转储规则配置好事后没法补也有些实现支持用诊断命令现场触发。区别很大别等出事才发现采不到。# 启动参数里预置转储规则堆转储 javacore落到固定大盘目录 java -Xmx4g \ -Xdump:heap:defaults:file/data/dump/heap-%Y%m%d-%H%M%S.phd,requestexclusiveprepwalk \ -Xdump:java:defaults:file/data/dump/javacore-%Y%m%d-%H%M%S.txt \ -jar app.jar这段参数里有三个细节值得展开。defaults表示规则对所有触发点生效包括 OOM、Full GC 之后、以及外部信号不用逐个事件写一遍。file里的%Y%m%d-%H%M%S是时间戳占位符我见过最惨的一次事故是文件名写死了第二次 OOM 直接把第一次的现场覆盖掉等于白采。requestexclusiveprepwalk让 JVM 在写文件前先做一次可达性预遍历产出的堆转储结构更完整、后续解析快很多代价是这次转储的停顿会变长生产上要么放低峰要么接受这几秒。# 现场补一份堆转储不需要提前配置代价是一次全堆遍历引起的停顿 jcmd 12345 GC.heap_dump /data/dump/heap-$(date %Y%m%d-%H%M%S).hprof # 只捞线程栈代价最低线上随时可做 # 连做三次、间隔 10 秒能看出线程是真的卡住还是恰好在跑 for i in 1 2 3; do kill -3 12345; sleep 10; donejcmd那条命令适合支持现场触发的 JVM 实现方便但会带来一次明显停顿几百 MB 的堆通常一到三秒几个 GB 的堆十几秒起步别在流量高峰做。kill -3那条几乎无代价只导出线程栈和锁信息是排查「接口卡住不动」的第一选择连做三次的价值在于排除偶然性如果三次里同一个线程都停在同一个栈帧上那才是真卡住。注意转储文件动辄几个 GB务必确认目标目录在独立的大容量分区上。我遇到过一次堆没采成反而把日志分区写满导致服务不可用比原来的内存问题严重得多。2.3 最小采集集与命名约定我的习惯是采一组而不是一个一份 javacore、一份 heapdump、一段覆盖故障时间窗的 verbosegc 日志外加一份当时的部署版本号和 JVM 启动参数。前三样给工具后两样给自己——没有版本信息你分析半天可能对着一份两周前就已经改过的代码。文件命名我固定用「服务名-类型-时间戳-触发原因」四段式比如order-svc-heap-20240612-1043-oom.phd。多份 dump 放在一起做差分的时候这个命名能省掉大量「哪份是基准、哪份是异常」的回忆成本也避免了靠文件修改时间猜顺序这种玄学操作。3. 装完第一次跑通从 workspace 卡死到泄漏疑点表工具本身是桌面程序解压即用不涉及服务端部署。真正耗时间的环节在第一次解析大文件这里有几个参数必须先调对否则会卡在进度条上怀疑人生。3.1 启动不了的三类原因第一类是运行时环境不匹配。PMAT 需要一个本地 Java 运行时来跑自己的界面和解析器它并不要求跟被测应用版本完全一致但位数必须匹配32 位运行时开不出 4 GB 以上的分析堆。第二类是 workspace 路径问题路径里带中文、空格或者落在网络盘上表现是启动后白屏或者建索引时长时间无响应。第三类是权限解压目录只读会导致配置写不进去起来就退出。调优入口是一个 ini 文件改完重启才生效# 分析器自身的堆上限解析 4 GB 的堆转储至少要给到 6 到 8 GB -vm /opt/jre-17/bin/java -vmargs -Xmx8g -XX:MaxDirectMemorySize2g -Dosgi.workspace.location/data/pmat-workspace-vm那行显式指定运行时路径比依赖 PATH 里的默认 java 稳定机器上装了多个版本时尤其重要。-Xmx8g是分析器自己的堆不是被测应用的堆经验比例是被测 dump 大小的 1.5 到 2 倍4 GB 的 dump 给 8 GB 起。MaxDirectMemorySize是给文件映射留的余量解析大文件时这块不够会抛出一堆看不懂的 IO 异常。workspace 换成绝对路径放在本地盘是我解决「打开就卡」最有效的一招。提示解析完成后工具会在 workspace 里缓存索引文件第二次打开同一份 dump 通常只要原来的零头时间。所以别频繁清 workspace除非你怀疑索引损坏。3.2 导入与解析进度条为什么长时间停在 80%导入时工具做的事比想象中复杂它要把二进制转储里所有对象读出来、按类分组、建立对象之间的引用图然后反向算出每个对象的保留大小。最后这一步是全局遍历进度条会长时间不动CPU 和磁盘都在忙这不是卡死。我的判断方法很土但有效看一眼进程的 CPU 占用如果在跑就等如果一直是 0八成是堆不够在反复 Full GC去日志里找OutOfMemoryError字样然后回来把-Xmx调大。一个 6 GB 的 dump 在普通笔记本上解析十几分钟是正常的别急着杀掉重来。解析完成后界面通常分四块类直方图、泄漏疑点、对象查询、线程视图。新手最容易犯的错是直接冲进泄漏疑点列表看到一个巨大的数字就开始怀疑某个框架类然后得出「这是框架的问题」这种没有意义的结论。正确的顺序是先看直方图建立整体印象再用支配树找持有者疑点列表放最后做交叉验证。3.3 泄漏疑点表的四个指标怎么读表格通常给四个数类名、实例数、浅大小、保留大小。浅大小是这个类的所有实例自身占的字节数保留大小是「如果把这些实例全回收掉能释放多少」——后者才是判断泄漏的依据因为它包含被这些实例独占持有的下游对象。举个真实感很强的例子一个HashMap$Node的浅大小可能只有 32 字节看着人畜无害但它的保留大小是 3 GB因为它持有的是整个缓存条目的引用链。只看浅大小排序前几名永远是String、char[]、byte[]这些「搬运工」它们是结果不是原因。指标含义什么时候最有价值实例数该类的对象个数判断有没有异常膨胀比如本该几百个的连接对象涨到几十万浅大小对象自身占用评估单个类的基础开销一般不单独用于定位保留大小回收它能释放的总量定位真正的内存持有者优先看这个排序支配关系谁直接持有它从「搬运工」顺藤摸瓜找到业务对象正确姿势是反过来走先按保留大小降序看前十名里有没有业务包名下的类如果前十名全是 JDK 集合类就点开它的支配树往上看一层看是被哪个业务类的字段持有的。这一步走通了基本就锁定了嫌疑范围。4. 查询与差分把「谁在涨」从猜测变成 3 个可核对的数字到这一步容易停在「我觉得是这个缓存类有问题」的层面结论没法写进复盘报告。要把它变成可核对的数字得靠查询和差分两个动作。4.1 直方图先粗后细先看搬运工再看持有者直方图按类聚合适合做第一轮筛查。我的固定动线是先看实例数最多的前十名找有没有明显不合理的量级再看数组类的长度分布超长数组往往是批量加载或者无界缓存的信号最后看集合类的实例数一个几十万条目的 Map 基本就说明了问题。这一轮不追求精确追求的是「有没有异常」。比如某个业务对象本来应该跟着请求走、请求结束就回收结果堆里有 40 万个实例那不管它单个多小都值得顺着引用链往上查。4.2 查询语句的三个固定套路查询是这类工具最被低估的功能比在界面上翻页效率高一个数量级。语法是类 SQL 的字段名对不上是常态——不同 JDK 实现的内部字段名会变所以正式写之前先到类浏览器里确认一下字段别直接抄网上的语句。-- 套路一限量展开某个业务类的实例看它持有什么 -- 先 LIMIT别一上来 SELECT *几十万条结果能把界面卡住 SELECT * FROM com.example.cache.Entry e LIMIT 200 -- 套路二按容量阈值筛专抓异常大的数组和集合 -- 这一条用来验证「有没有无界缓存」这个假设 SELECT * FROM java.util.HashMap h WHERE h.size 100000 -- 套路三按成员值筛定位到某一类业务特征的对象 -- 适合已知出问题的租户/类型直接缩小范围 SELECT * FROM com.example.order.Order o WHERE o.status PENDING第一条的价值在「看结构」200 条足够你判断这个对象的字段里有没有不该长期存在的引用。第二条是验证无界缓存最快的办法一次查询就能给出「有几个 Map 超过十万条目」这个数字比翻界面快得多。第三条适合多租户场景当怀疑是某个大客户的请求把内存撑爆时直接按业务字段筛命中数就是你的证据。字段名对不上时的处理办法在类浏览器里点开目标类它会列出真实字段名和作用域照着改。这一步花两分钟比反复试错快。4.3 差分对比只看增量不看绝对量这是我最想强调的一条。单份 dump 的 top N 永远是那几个 JDK 集合类因为任何正常运行的 Java 应用里它们都占大头。只看绝对量你会得出「所有 Java 应用都在泄漏」这种荒唐结论。差分才是定位泄漏的正道采两份间隔足够长的 dump做对象数量和大小的差集只看增量最大的那些类。一次典型的操作是服务启动稳定后采一份基准跑满一个完整业务周期比如一天里最忙的那个时段后再采一份两份之间增量的前几名几乎必然是泄漏的源头。差分有个前提容易被忽略两份 dump 必须可比。负载特征不同、时间点不同、连缓存预热状态都不同的两份文件差分出来全是噪声。我一般要求两次采样都在低峰前一刻、间隔至少半小时、期间没有发布变更。不满足这三条差分结果只能当参考。4.4 javacore 那一半线程与锁堆转储解决「内存被谁占了」javacore 解决「卡在哪」。工具会把线程按状态分组重点看三类长时间处于等待状态的线程、处于阻塞状态并且指向同一把锁的线程、以及栈顶停在同一个方法上的大批线程。死锁检测是自动的能直接给出环路。但更多时候没有严格死锁只有「线程池的线程全被同一个下游调用占住」这种软性耗尽工具不会报警得你自己看栈顶分布如果 200 个工作线程里有 180 个栈顶都停在同一个 HTTP 客户端的方法上那就是下游拖垮了上游跟内存一点关系都没有。5. 避坑记录五个让我重采一遍 dump 的现场这些是实打实重采过的不是从手册上抄的。每一条都按「现象、原因、解决」写方便对照。5.1 采集阶段的两个坑坑一采出来的文件是 0 字节。现象很直接一次 OOM 之后兴冲冲去拿文件发现只有名字没有内容或者只有一份 javacore 没有 heap。原因通常是两条目标目录所在分区满了写出过程中断了或者进程的运行用户对目标目录没有写权限转储被静默丢弃。解决分两步先df -h看分区再确认运行用户身份和目录属主转储目录单独挂一个大盘并提前授权。这一条的成本极低但漏掉的代价是整个现场要等下一次复现。坑二转储把服务拖垮了。现象是在流量高点触发堆转储服务僵住几十秒健康检查直接把实例拉出集群。原因是写堆转储本质是一次全堆遍历堆越大停顿越长prepwalk还会再加一部分时间。解决办法是分层真出问题时先用kill -3拿线程栈和一份 verbosegc 片段做快速判断确认是内存问题再决定要不要采堆采堆放在低峰或者对实例按组轮流操作不要一次全采。另外如果只是要判断「有没有泄漏」两份间隔的堆转储远比连续多份有用别浪费停顿预算。5.2 分析阶段的三个坑坑三打开 dump 就卡死动不动抛出内存错误。现象是导入进度条走到一大半停住日志里出现分析器自己的内存溢出。原因很明确是分析器自身堆不够跟被测应用无关。解决是把-Xmx调到 dump 大小的 1.5 到 2 倍同时确认 workspace 在本地盘上。这里有个反直觉的点给分析器 8 GB 堆比给 16 GB 更好堆太大一来 GC 时间长二来普通办公机根本拿不出这么多物理内存会开始换页表现反而更慢。坑四疑点列表全是框架类找不到业务对象。现象是按默认排序看到的全是String、HashMap$Node、byte[]没有任何一个业务包名下的类。原因是看的是浅大小排序。解决是切到保留大小排序再从HashMap$Node点进支配树往上找一层持有者。这个过程通常一到两层就能撞见业务类的字段名撞见之后立刻用 4.2 里的查询语句按类去查把数量坐实。坑五差分结果全是噪声。现象是两份 dump 差分出来增量前几十名互相矛盾一会儿是这个类多了一万个一会儿是那个类少了八千。原因是两份采样不可比间隔太短、负载不同、或者中间发生过发布重启。解决办法是定三条硬规则——间隔至少半小时、两次都在类似的负载水平、期间没有代码或配置变更。做不到就别做差分单份 dump 配合 verbosegc 的斜率也能出结论只是证据链弱一点。注意这五个坑里有两个0 字节、拖垮服务会导致现场彻底丢失而它们的预防成本几乎为零。先确保转储配置正确再讨论用什么工具分析顺序不能反。6. 进阶验证把疑点变成可复现的假设再关掉一次告警分析到最后一定会落到一个具体假设上比如「这个本地缓存的 key 里混进了请求 ID导致容量永远清不干净」。此时最大的风险是拿着假设直接去改代码改完发现堆还是涨白干一轮。我的做法是给每个假设配一个验证手段和通过标准三条全过才算结案。假设结论验证手段通过标准某个 Map 无界增长差分两份 dump看该类实例数增量连续两个业务周期增量接近 0缓存 key 命中率低在 dump 里按 key 前缀分组统计前缀种类数收敛到业务实体数量级大对象被上游长期持有支配树逐层展开找到持有者字段持有者生命周期与业务语义一致具体操作上我会在改动上线后重跑一遍完整链路先采一份启动稳定后的基准堆跑满同样时长再采第二份做差分看目标类的实例数增量是不是从几千掉到了个位数。同时把 verbosegc 的老年代曲线拉出来对比看斜率有没有变平。两个信号同时转好才把那条告警规则关掉。有个习惯我保持了几年任何一次「修好了」的结论都必须有一份改动后的 dump 作为证据而不是「观察了几天没再报」。因为没再报的可能原因太多了——流量下降了、实例扩了、告警阈值被调了——这些都跟修复无关。在一次又一次重采 dump 的过程中我最大的教训是先怀疑采样条件再怀疑代码顺序错了会浪费整晚。从那以后我每次开分析之前都会先把文件的时间戳、版本号、负载水平三样东西写在便签上贴在屏幕边上对不上就先重采不硬分析。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询