生产环境遇到 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 工具:
# Arthas heapdump 命令,支持指定 live 对象
heapdump `--live` /tmp/heap.hprof传统 jmap 命令:
jmap -dump:format=b,file=heap.hprof <pid>3. 初步查看
jmap -histo <pid> | head -n 20MAT 分析关键步骤
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"。查看是谁持有了这个集合,常见路径是静态字段或长期存活的单例对象。
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 调用,尤其在线程池场景下。