★ JVM 的 OOM 有哪些常见类型?线上如何排查定位?
结论先行:OOM(OutOfMemoryError)是 JVM 内存耗尽时抛出的异常,按抛出位置可分为堆、元空间、本机线程、堆外等几类。排查主链路是“先看异常关键字分类,再取证据(jstat/jmap/堆快照),最后归因到代码或参数”;遇到 Java heap space 时,多数根因是“对象只进不出”的泄漏或瞬时峰值超配,而不是简单地把 Xmx 调大。
常见类型与应对速查表
| 异常信息 | 内存区域 | 典型根因 | 优先动作 |
|---|---|---|---|
| Java heap space | 堆 | 泄漏 / 峰值超配 / 超大数组 | 导出堆快照分析,必要时调 Xmx |
| GC overhead limit exceeded | 堆 | GC 空转却几乎回收不动 | 先定位谁占堆,不要先加内存 |
| 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> # 导出堆快照(先评估暂停与磁盘)
堆快照归因思路
- MAT 打开快照,先在 Histogram 里找“浅堆”最大的几个类;
- 用 Dominator Tree 找支配树根部,定位真正持有内存的大对象;
- 沿“GC Roots → 嫌疑对象”的引用链检查:静态字段、ThreadLocal、监听器、连接池最常见背锅;
- 把结论落到“泄漏(只增不减)”还是“峰值(回落正常)”,再决定改代码还是调参数。
泄漏示例
// 反例:无界静态集合承载请求数据,只写不清理 → 最终 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 引用链才是定案。