首要目标是稳住服务、保留线索、缩小范围;第一步确认oom类型并交叉验证日志与jstat指标,第二步禁止重启、立即dump堆快照,第三步用jmap-histo等命令行工具三分钟快速归因,第四步针对性临时缓解。

发现内存溢出(OOM)时,首要目标不是立刻调参或改代码,而是快速稳住服务、保留关键线索、缩小问题范围。响应节奏越快,越容易抓住“活体现场”,避免重启后线索丢失。
第一步:确认是否真发生OOM并锁定类型
别只看日志关键词,要结合错误堆栈和JVM行为交叉验证:
- 查日志中完整的异常信息——java.lang.OutOfMemoryError: Java heap space 是堆溢出;Metaspace 是类元数据问题;Direct buffer memory 指堆外内存;GC overhead limit exceeded 表明GC已失效。
- 立即执行
jstat -gcutil <pid> 1000 3</pid>,观察老年代(O)是否持续 >95%、Full GC次数(FGC)是否陡增、元空间(M)是否逼近上限。 - 若进程还在运行但响应极慢,用
jstack <pid> > thread.log</pid>快速抓取线程快照,重点看是否有大量 WAITING/RUNNABLE 线程堆积在缓存、IO 或锁操作上。
第二步:保现场,防重启误操作
很多团队第一反应是重启,但这是最大误区——OOM 的根因往往藏在“未释放的对象引用链”里,重启等于清空证据。
- 如果 JVM 还活着且没自动 dump,立刻执行:
jmap -dump:live,format=b,file=/tmp/oom-$(date +%s).hprof <pid></pid> - 确认是否已开启自动 dump:检查启动参数是否有
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path;如有,直接去对应路径找最新 .hprof 文件。 - 禁止手动 kill -9 或滚动重启,除非服务已完全不可用且有降级预案;优先做流量切换或限流,为分析留出时间窗口。
第三步:轻量级快速归因(不依赖MAT)
等 MAT 分析耗时长,先用命令行工具做三分钟定位:
- 用
jmap -histo <pid> | head -20</pid>查实例数最多的前20个类——如果byte[]、String、HashMap$Node占比异常高,大概率是大对象或缓存泄漏。 - 结合
jstat -gccapacity <pid></pid>看各代容量分配是否严重失衡(如老年代仅占堆 30%,却长期 98% 满)。 - 检查是否有
System.gc()调用痕迹(grep 代码或用 Arthas trace 命令实时监控);若有,立即下线相关逻辑。
第四步:临时缓解与上线止损
在根因未修复前,需防止二次崩溃:
- 对堆溢出:若确认是瞬时峰值(如大报表导出),可临时加限流(如 Sentinel QPS 控制)或拆分任务,而非盲目调大 -Xmx——这可能掩盖泄漏,让问题延后爆发。
- 对 Metaspace 溢出:加
-XX:MaxMetaspaceSize=512m并观察是否延缓;同时排查 CGLIB/Groovy 动态代理是否重复生成同类。 - 对 Direct Memory:检查 Netty 或 NIO 代码中
ByteBuffer.allocateDirect()是否未配合cleaner释放,或补上-XX:MaxDirectMemorySize=256m。
响应不是终点,而是把“发生了什么”变成“为什么发生”的起点。真正解决靠的是从 dump 中定位到具体类和引用链,但快速响应决定了你有没有这个机会。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











