3. Heap Dump 概念与价值:快照内容、分析用途、触发时机

3.1 现象识别

当生产环境出现以下任一信号时,表明堆内存可能已接近崩溃边缘,需要立即介入:

  • 应用突然停滞或完全无响应,监控显示堆使用率连续多轮 Full GC 后仍不下降。
  • 连续抛出 java.lang.OutOfMemoryError: Java heap space,或 GC overhead limit exceeded。
  • 每秒 GC 频率异常升高,Young 区回收后存活对象迅速填满老年代。
  • 关键业务接口响应时间陡增,CPU 占用却保持在较低水平(频繁 GC 挂起线程)。
关键判断规则: 如果 jstat -gcutil 显示老年代使用率超过 90% 且 FGCT 持续增长,概率上已经存在内存泄漏,Heap Dump 是唯一能直接定位泄漏源的证据。

3.2 数据采集:Heap Dump 的触发时机与快照内容

3.2.1 触发时机对比表

触发方式 命令 / 参数 适用场景 注意事项
OOM 自动触发
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/dump.hprof
线上生产环境,首次 OOM 分析 确保磁盘空间充足(通常为堆大小的 1.2~1.5 倍);生产环境建议保留最近 3 次 dump
手动 jmap
jmap -dump:format=b,file=heap.hprof <pid>
压测中出现疑似泄漏,应用仍可运行 执行时应用会暂停(Stop-The-World),高负载时慎用
自动转储工具
-XX:HeapDumpAfterFullGC -XX:HeapDumpBeforeFullGC
需要对比 GC 前后堆变化 会带来额外的 I/O 开销,低频 Full GC 场景使用
JMX 远程触发 通过 HotSpotDiagnosticMXBean.dumpHeap() 容器化环境,无法直接 SSH 到节点 需要开启 JMX 端口,注意安全隔离

3.2.2 Heap Dump 快照包含的核心内容

一份标准的 .hprof 二进制文件记录了以下四类信息,缺一不可:

  1. 对象实例清单:每个存活对象的类、大小、内存地址,以及实例字段的值(字符串、数组结构等)。
  2. 类加载信息:所有已加载的类定义、类加载器、静态字段值。
  3. 引用链:从 GC Roots(栈、静态变量、JNI 引用)到每个对象的完整引用路径。
  4. 线程堆栈:每个线程的调用栈,可关联到持有大对象的代码位置。
采集陷阱: 如果应用同时开了 -XX:+UseCompressedOops 且堆大小超过 32GB,部分工具可能无法正确解析指针。建议在生产环境中保持 -Xmx < 32G 以兼容主流分析工具。
Heap Dump 分析闭环流程 OOM / 高 GC 生成 Heap Dump 分析工具(MAT) 泄漏根 / 大对象 修复代码 → 复测 设置自动 Dump 参数 堆使用率预警 定期压测对比 预防闭环:避免二次泄漏

3.3 原因定位:故障树与排查表

以下故障树归纳了 Heap Dump 分析中常见的根因分类,每个叶子节点对应一种分析路径:

故障树层级 1 故障树层级 2 Heap Dump 判断依据 常见案例
内存泄漏 集合类未清理 某 HashMap / ArrayList 对象持续增长,无 GC 回收 缓存未设置上限,ThreadLocal 未 remove
回调/监听器未注销 观察者模式中注册对象数量随时间线性增加 EventBus 注册后忘记 unregister
资源泄漏 大量 java.sql.Connection / Socket 对象未被 close() 连接池配置泄漏或使用 try-with-resources 不完整
大对象问题 单个大数组/大字符串 单个对象占用超过老年代 30% 以上 一次性加载全表数据到内存
大量小对象碎片 同一种类对象数量超过百万级,每个仅几十字节 频繁创建 Date、StringBuilder 等短生命周期对象
配置不当 堆容量本身不足 存活对象总量接近 -Xmx,但无明显泄漏曲线 默认堆太小,压测时正好达到瓶颈

3.3.1 快速定位规则(以 MAT 为例)

  1. 打开 Heap Dump → 运行 Leak Suspects Report(MAT 自动生成)。
  2. 观察最大对象列表:Histogram 视图按 Retained Heap 排序,排查前 5 个。
  3. 分析 GC Roots 路径:选中可疑对象 → Merge Shortest Paths to GC Roots → 跳过弱引用/软引用。
  4. 交叉验证线程栈:查看哪个线程的 Thread Local 或局部变量持有该对象。
经验规则: 如果 Dominator Tree 中排名第一的对象占用了堆的 70% 以上,这就是泄漏点;如果排名前 5 的对象都很平均且无明显增长,大概率是“大对象”或“配置不足”,而非泄漏。

3.4 修复动作(基于 Heap Dump 分析结果)

根因类型 修复动作示例 对应代码片段
集合泄漏 改用 WeakHashMap 或显式 clear()
// 修复前
private static final Map cache = new HashMap<>();
// 修复后
private static final Map cache = new WeakHashMap<>();
// 或增加定时清理
cache.keySet().removeIf(k -> !k.isValid());
资源泄漏 使用 try-with-resources 或显式 close
// 修复前
Connection conn = pool.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
// 忘记关闭
// 修复后
try (Connection conn = pool.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
  // 业务逻辑
}
大对象加载 增加分页查询或流式处理
// 修复前
List all = orderRepo.findAll();  // 全量加载
// 修复后
Page page = orderRepo.findAll(PageRequest.of(0, 500));
while (page.hasNext()) { page = orderRepo.findAll(page.nextPageable()); }

3.5 复测:验证 Heap Dump 不再异常增长

  1. 压力复测:使用相同并发量运行业务场景 30 分钟以上。
  2. 连续采集两次 Heap Dump(间隔 10 分钟),对比 Histogram 差异:如果所有类的 Number of Objects 增幅在 5% 以内,泄漏已消除。
  3. 监控指标确认:老年代使用率曲线应趋于平稳,Full GC 频率恢复至正常范围(每小时 0~2 次)。
复测常见误区: 只单次采集一个 Heap Dump 就认为没问题。务必在两个不同时间点采集,比较对象数量变化,才能排除“恰好 GC 后”的假象。

3.6 预防:将 Heap Dump 纳入日常运维

  • JVM 参数固化:生产环境必须添加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/dump/,并确保路径有写入权限。
  • 定时清理策略:使用 logrotate 或自定义脚本保留最近 3 次 dump,避免磁盘写爆。
  • 主动预警:监控 jstat 的老年代占比,当超过 80% 时触发告警并自动生成一次 Heap Dump(通过 JMX 或 jcmd)。
  • 定期压测对比:每周对核心接口做一次短时间压测,采集 Heap Dump 入库,观察对象数量趋势。

Heap Dump 不是事后补救工具,而是预防性分析的关键数据源。将 Heap Dump 的生成与分析自动化,能在大规模 OOM 爆发前 48 小时捕捉到泄漏信号。