生产环境 Java 集合内存溢出 OOM 怎么通过 Heap Dump 排查?

文章导读
生产环境遇到 Java 集合导致的内存溢出,最稳妥的办法是保留现场生成 Heap Dump 文件,再用 MAT 工具分析对象引用链,定位到具体哪个集合类只增不减。
📋 目录
  1. A 生产环境安全导出 Heap Dump
  2. B MAT 分析关键步骤
  3. C 代码修复前后对比
  4. D 验证与监控
  5. E 常见风险与坑
A A

生产环境遇到 Java 集合导致的内存溢出,最稳妥的办法是保留现场生成 Heap Dump 文件,再用 MAT 工具分析对象引用链,定位到具体哪个集合类只增不减。

先说结论:不要急于重启,优先保留内存快照,通过支配树和泄漏嫌疑报告找到占用最大的集合对象。

  • 先确认:区分是堆内存溢出还是元空间溢出,确认错误类型为 Java heap space。
  • 先处理:配置 JVM 参数自动 dump 或手动 jmap 导出堆快照,避免重启丢失现场。注意生产环境手动 dump 风险
  • 再验证:修复代码后观察 GC 日志和内存曲线,确认老年代不再持续增长。

生产环境安全导出 Heap Dump

1. 预防配置(推荐)

在 JVM 启动参数中配置自动 dump,避免故障时手动操作风险:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dumps/

注意:确保 dump 路径所在磁盘有足够空间(至少等于堆内存大小),使用 df -h 检查,防止 dump 文件占满磁盘导致系统崩溃。

2. 手动导出(应急)

若未配置自动 dump 且进程未完全假死,可尝试手动导出。但 OOM 时进程响应极慢,jmap 可能加重负担导致彻底挂起无法重启。

优先检查自动 dump 文件是否存在。若必须手动操作,建议低峰期进行,或使用 Arthas 工具:

生产环境 Java 集合内存溢出 OOM 怎么通过 Heap Dump 排查?
# Arthas heapdump 命令,支持指定 live 对象
heapdump `--live` /tmp/heap.hprof

传统 jmap 命令:

jmap -dump:format=b,file=heap.hprof <pid>

3. 初步查看

jmap -histo <pid> | head -n 20

MAT 分析关键步骤

1. 查看泄漏嫌疑报告

打开 hprof 文件后,优先查看 "Leak Suspects Report" 自动分析报告,通常能直接定位到占用内存最大的集合类。

2. 支配树分析

进入 "Dominator Tree",按 "Retained Heap" 降序排序。注意不要只看 "Shallow Heap",前者只算对象本身大小,后者才算真正占用的内存。

3. 定位引用链

对可疑的大对象右键选择 "Merge Shortest Paths to GC Roots" -> "exclude all phantom/weak/soft etc. references"。查看是谁持有了这个集合,常见路径是静态字段或长期存活的单例对象。

生产环境 Java 集合内存溢出 OOM 怎么通过 Heap Dump 排查?

4. 过滤干扰

分析时可过滤掉 JDK 自带类,聚焦业务包名。若对象过多,可使用 OQL 查询特定类实例。

代码修复前后对比

错误示例:静态集合无限增长

// 风险代码:静态 List 无清理机制
public class CacheManager {
    private static List<String> cache = new ArrayList<>();
    
    public void add(String data) {
        cache.add(data); // 只增不减,最终 OOM
    }
}

修复方案 1:增加清理策略或上限

// 修复代码:使用 LinkedHashMap 实现 LRU 缓存
public class SafeCache {
    private static Map<String, String> cache = new LinkedHashMap<String, String>(100, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
            return size() > 1000; // 限制最大大小
        }
    };
}

修复方案 2:使用弱引用或专业缓存库

// 修复代码:使用 WeakHashMap 或 Guava/Caffeine
private static Map<String, Object> cache = new WeakHashMap<>();
// 或推荐生产使用 Caffeine
private static Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(10000).build();

验证与监控

部署修复后的版本,开启 GC 日志(-Xloggc 或 -XX:+PrintGCDetails)。

  • 观察 Full GC 频率是否降低。
  • 观察老年代内存使用率在业务高峰后是否能回落到正常水平,而不是持续上升。
  • 通过监控系统(如 Prometheus)观察 heap_used 曲线是否平稳。

常见风险与坑

1. 磁盘空间:dump 文件大小接近堆内存大小,务必确认磁盘剩余空间,防止写满导致系统宕机。
2. STW 停顿:生产环境 dump 会导致 STW(服务暂停),大内存应用可能卡顿几分钟,需在低峰期或备机操作。
3. 进程假死:OOM 时进程可能已假死,jmap 操作可能导致无法重启,优先检查自动 dump 文件。
4. ThreadLocal 泄漏:线程局部变量使用不当也会导致内存泄漏,需确保 remove 调用,尤其在线程池场景下。