凌晨收到告警:服务频繁重启。登录发现 OOM Killer 杀了 Java 进程。本文是完整的排查和修复复盘。

一、告警与发现

  • 1.1 监控告警——服务重启次数陡增
  • 1.2 第一时间确认——dmesg | grep -i kill,OOM Killer
  • 1.3 临时止血——重启服务,增加内存(-Xmx 调大)

二、保留现场

  • 2.1 GC 日志——-Xlog:gc* 输出
  • 2.2 堆 dump——-XX:+HeapDumpOnOutOfMemoryError
  • 2.3 线程 dump——jstack
  • 2.4 系统指标——CPU、内存趋势

三、GC 日志分析

  • 3.1 Full GC 频率异常——每 30 秒一次
  • 3.2 老年代持续增长——内存泄漏的典型特征
  • 3.3 GCViewer 可视化

四、MAT 分析堆 dump

  • 4.1 Leak Suspects Report——HashMap 占用 2GB
  • 4.2 Dominator Tree 追踪——某个缓存 Map 无限增长
  • 4.3 Path to GC Roots——由静态变量持有
  • 4.4 OQL 查询验证——上千个 Key 从未被清理

五、根因定位

  • 5.1 代码审查——本地缓存的 ConcurrentHashMap,只有 put 没有 remove
  • 5.2 为什么上线时没发现——数据量小的时候不明显
  • 5.3 触发条件——业务量增长后缓存膨胀

六、修复方案

  • 6.1 短期——加 maximumSize + LRU 淘汰(Caffeine 替换)
  • 6.2 长期——切换到 Redis 分布式缓存
  • 6.3 监控——添加缓存大小的 JVM 指标

七、预防措施

  • 7.1 代码审查规范中加入”缓存必须有淘汰策略”
  • 7.2 压测中增加长时间稳定性测试
  • 7.3 监控告警——堆内存使用率 > 80% 预警

八、复盘总结

  • 在测试环境难以发现的”慢性”内存泄漏
  • 缓存使用三原则——大小限制、淘汰策略、过期时间