一份规范的JVM内存泄漏排查报告需明确问题现象、排查路径、根因代码、修复方案及原始材料索引:如老年代使用率4小时内从45%升至92%,Full GC频次激增至8次/分钟;通过jstat、jmap和MAT分析定位到CacheManager.addToCache方法中静态ConcurrentHashMap无淘汰机制导致byte[]缓存堆积;短期加LRU限制10000条,长期迁入Caffeine;灰度后老年代稳定在30%±5%,Full GC归零;附GC日志路径、dump文件名、MAT截图编号及commit hash。

一份规范的 JVM 内存泄漏排查报告,核心是让读者(尤其是协作方或后续接手人)能快速理解“发生了什么、怎么发现的、如何验证、根因在哪、怎么修复”,避免信息碎片化或主观推测。它不是日志堆砌,而是有逻辑、可复现、可追溯的技术文档。
明确问题现象与监控依据
开头直接说明异常表现,用客观数据支撑,不写“感觉变慢”“好像内存高”这类模糊描述:
- 监控平台截图或关键指标:如 ARMS/云监控中老年代使用率连续 4 小时从 45% 涨至 92%,且 Full GC 频次由 1 次/2 小时升至 8 次/分钟;
- GC 日志关键片段:摘录 2–3 行典型日志,标注时间、GC 类型、老年代回收前后大小(例如 O: 1820M->1795M),并指出回收率仅 1.4%,远低于健康阈值(通常应 >30%);
- OOM 或告警记录:如 10月10日 14:22 触发
java.lang.OutOfMemoryError: Java heap space,自动生成 dump 文件/data/dump/heap_20261010_1422.hprof。
记录完整排查路径与工具输出
按实际操作顺序还原分析过程,每一步都注明工具、命令、目的和关键输出,方便他人复现或交叉验证:
-
jstat -gcutil
2s 5 :确认老年代持续增长 + FGCT 占比超 40%,初步排除新生代配置不当; -
jmap -histo
| head -n 20 :发现com.example.cache.DataHolder实例数达 24 万,占堆对象总数 68%; -
jmap -dump:format=b,file=heap.hprof
:在 GC 异常高峰期手动触发 dump,文件大小 1.2GB,MD5 校验值附后; - MAT 打开后使用 Leak Suspects Report,定位到 1 个主导泄漏的
HashMap,其 Retained Heap 占总堆 73%;点击支配树(Dominator Tree),下钻至 key 类型为String、value 为byte[]的缓存项。
定位根因代码与复现方式
不只说“缓存没清理”,要精确到类、方法、调用链和触发条件:
- 问题代码位置:
com.example.service.CacheManager.addToCache(String key, Object value)(第 47 行); - 关键缺陷:该方法将外部传入的
byte[]直接 put 进静态ConcurrentHashMap<string byte></string>,未做大小限制、无过期机制、无淘汰策略; - 复现步骤:用 JMeter 发送 5000 个含 Base64 图片字段的 POST 请求(请求体约 200KB/次),10 分钟内即可复现老年代涨至 95%;
- 佐证证据:在测试环境添加
-XX:+HeapDumpOnOutOfMemoryError后复现,MAT 分析结果与线上一致。
修复方案与验证结论
给出可落地的修改建议,并说明已验证效果:
- 短期修复:在
addToCache中增加容量检查与 LRU 淘汰(基于LinkedHashMap),限制最大条目数为 10000; - 长期优化:替换为 Caffeine 缓存,配置
maximumSize(10000)+expireAfterWrite(30, TimeUnit.MINUTES); - 验证结果:灰度发布后,相同压测场景下老年代稳定在 30%±5%,Full GC 归零,接口 P99 延迟下降 62%;
- 补充建议:在 CI 流程中加入 MAT 自动扫描(通过
mat.sh脚本解析 dump),拦截类似静态集合滥用。
不复杂但容易忽略的是:报告末尾附上所有原始材料索引——GC 日志路径、dump 文件名及存储位置、MAT 分析截图编号、对应 commit hash。这样一份报告,既是问题闭环的凭证,也是团队知识沉淀的入口。


















