★ JVM 的 OOM 有哪些常见类型?线上如何排查定位?

结论先行:OOM(OutOfMemoryError)是 JVM 内存耗尽时抛出的异常,按抛出位置可分为堆、元空间、本机线程、堆外等几类。排查主链路是“先看异常关键字分类,再取证据(jstat/jmap/堆快照),最后归因到代码或参数”;遇到 Java heap space 时,多数根因是“对象只进不出”的泄漏或瞬时峰值超配,而不是简单地把 Xmx 调大。

常见类型与应对速查表

异常信息内存区域典型根因优先动作
Java heap space泄漏 / 峰值超配 / 超大数组导出堆快照分析,必要时调 Xmx
GC overhead limit exceededGC 空转却几乎回收不动先定位谁占堆,不要先加内存
Metaspace元空间反射/动态代理/热部署生成类过多查类加载来源,调 MaxMetaspaceSize
unable to create native thread本机线程线程数触顶(ulimit/内存不足)排查线程泄漏与线程池配置
Direct buffer memory堆外DirectByteBuffer 未释放查 NIO / Netty 缓冲使用
StackOverflowError虚拟机栈递归过深修代码;属栈问题,严格说不算 OOM

取证命令

jstat -gcutil <pid> 1000                      # 各区使用率 + YGC/FGC 次数与耗时
jmap -histo:live <pid> | head -20             # 存活对象分布,先找大头
jmap -dump:format=b,file=/tmp/h.hprof <pid>   # 导出堆快照(先评估暂停与磁盘)

堆快照归因思路

  1. MAT 打开快照,先在 Histogram 里找“浅堆”最大的几个类;
  2. 用 Dominator Tree 找支配树根部,定位真正持有内存的大对象;
  3. 沿“GC Roots → 嫌疑对象”的引用链检查:静态字段、ThreadLocal、监听器、连接池最常见背锅;
  4. 把结论落到“泄漏(只增不减)”还是“峰值(回落正常)”,再决定改代码还是调参数。

泄漏示例

// 反例:无界静态集合承载请求数据,只写不清理 → 最终 Java heap space
public class SessionHolder {
    static final Map<String, byte[]> SESSIONS = new ConcurrentHashMap<>();

    public static void onRequest(String id, byte[] payload) {
        SESSIONS.put(id, payload); // 无过期、无容量上限、无清理入口
    }
}

常见追问 / 记忆点

  • 追问:Full GC 频繁但没 OOM 怎么继续查?答:jstat 看 FGC 频率与耗时,jmap -histo:live 看存活对象分布。
  • 追问:生产堆很大,jmap 导出会不会卡服务?答:可提前配 -XX:+HeapDumpOnOutOfMemoryError,让 JVM 在 OOM 时自动落盘留证。
  • 记忆点:先分类、再取证、后归因;“Xmx 调大”只是止血,快照 + GC Roots 引用链才是定案。
笔记加载中…