OOM 排查:先看异常类型,再定位是哪块内存
更新时间:2026-09-20。
OutOfMemoryError不是单一问题,后缀决定了是哪块内存耗尽、排查方向完全不同。配合堆转储与 GC 数据才能区分「真泄漏」还是「容量不够」。基础见 JVM 堆分代、jmap 堆转储,GC 视角见 jstat 与 GC 日志。
一、五种常见 OOM
| 异常后缀 | 耗尽的区域 | 典型原因 |
|---|---|---|
| Java heap space | 堆 | 内存泄漏、缓存无限涨、一次加载超大结果集/大数组 |
| GC overhead limit exceeded | 堆 | GC 占了 >98% 时间却回收不到 2%,是堆将满的前兆 |
| Metaspace(老版本 PermGen) | 元空间 | 类加载泄漏:动态代理、CGLIB、热部署、大量脚本引擎 |
| unable to create new native thread | 本机线程/进程限制 | 线程池/连接泄漏、无界 new Thread、ulimit -u 太小 |
| Direct buffer memory | 堆外直接内存 | NIO/Netty 用了不释放的 ByteBuffer,MaxDirectMemorySize 太小 |
二、通用第一步:让它留下现场
bash
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/
-XX:+ExitOnOutOfMemoryError # OOM 后直接退出,让容器/守护进程重启,避免僵死
-Xlog:gc*:file=/data/gc/app-gc.log:...生产 JVM 默认就该加 HeapDumpOnOutOfMemoryError:OOM 瞬间自动 dump,事后用 Eclipse MAT / JProfiler 分析;没有 dump 时只能临时 jmap -dump:live,format=b,file=h.hprof <pid>(会触发 Full GC、STW,慎用)。
三、堆 OOM 的定位流程
bash
1. 确认现象:jstat 看老年代是否 Full GC 后仍不回落(真泄漏)还是只是堆设小
2. 拿 dump:MAT 打开 hprof
3. 看 Leak Suspects / Histogram:按 Retained Heap 排序找占内存最大的类
4. 看 Dominator Tree:谁把持着最多对象
5. Path to GC Roots:排除弱/软引用,找到是谁一直引用导致无法回收
6. 定位代码:静态集合、自定义缓存、ThreadLocal 未 remove、监听器/连接未关
四、非堆 OOM 怎么查
- Metaspace:
jcmd <pid> GC.class_stats(需开启)或 MAT 看 Class Loader Explorer,重点找重复创建、本应卸载却还活着的 ClassLoader;设-XX:MaxMetaspaceSize让问题尽早暴露。 - native thread:
jstack数线程状态、看是不是同类线程海量创建;系统侧ulimit -u、/proc/<pid>/tasks、ss -s配合,根因通常是无界线程池或连接池,见线程池参数。 - 直接内存:堆 dump 里看不到(不在堆),要靠 NMT(
-XX:NativeMemoryTracking=summary加jcmd <pid> VM.native_memory summary)或 Netty 的内存指标;检查ByteBuffer.allocateDirect后是否 close、是否因-XX:+DisableExplicitGC导致 Cleaner 不回收。
五、是泄漏还是容量不足
- 泄漏:Full GC 后老年代占用仍阶梯式抬升、每次重启后能撑的时间越来越短;dump 中某业务类实例数异常。修复在代码(移除无界缓存、加淘汰、关资源、清 ThreadLocal)。
- 容量不足/突发:堆在 Full GC 后能回到低位,只是峰值数据确实大;先看能否分页/流式处理、限制批量大小,确有必要再调大
-Xmx并确认容器内存上限(别超出被 OOMKilled)。
坑
- 只调大 -Xmx 不查根因:泄漏会让更大的堆只是晚一点崩,还可能拖出更长 STW。
- 容器里 Xmx 超过内存 limit:JVM 看似没 OOM,进程却被 cgroup OOMKilled;Java 10+ 用
-XX:MaxRAMPercentage感知容器。 - dump 文件把磁盘写满:预留与堆等大的磁盘空间并轮转。
- 把 GC overhead 当独立故障:它本质是堆快满的信号,按 Java heap space 同样查。
- 线上临时 jmap --dump 引发长 STW:优先依赖 OOM 自动 dump,手动 dump 要在低峰并评估停顿。
总结
排查 OOM 先按后缀分诊:Java heap space 与 GC overhead 指向堆泄漏或峰值过大,用自动 HeapDump + MAT 从直方图、支配树一路看到 GC Root 找静态集合/无界缓存/未清理的 ThreadLocal;Metaspace 查 ClassLoader 与动态代理泄漏;native thread 查无界线程池与 ulimit;Direct buffer 靠 NTT/Netty 指标查堆外 ByteBuf 释放。务必默认开启 HeapDumpOnOutOfMemoryError,并先用 Full GC 后老年代能否回落区分真泄漏与容量不足,修代码优先于盲目加内存。 相关诊断工具见 jmap 与 Arthas。