TL;DR(30 秒扫完)
- OOM 完整 8 类:Java heap space / GC overhead / Metaspace / Direct buffer / thread / array size / compressed class / continuation
- 救命参数组合:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=... -XX:+ExitOnOutOfMemoryError - 排查三件套:堆 dump(MAT)+ GC 日志(
-Xlog:gc*)+ 原生内存(jcmd VM.native_memory) - DirectBuffer 泄漏:Netty ByteBuf 未 release,JDK 9+ 用 Cleaner
- GC overhead 通常是堆溢出的前兆
关键结论
结论 AOOM 是 Error 而非 Exception,通常 JVM 不会立即退出(除特定类型或 GC 前最后一次尝试失败)
结论 BGC overhead limit exceeded 是堆溢出的前兆——GC 努力回收但效果差
结论 C救命参数
-XX:+HeapDumpOnOutOfMemoryError 是生产上最重要的排查手段结论 D不同 OOM 类型需不同排查路径(Metaspace 查 ClassLoader、DirectBuffer 查 Native Memory)
结论 E
-XX:+ExitOnOutOfMemoryError 让容器快速重启,配合 dump 保留现场完整讲解(费曼四步)
STEP 1 · 概念
OOM(OutOfMemoryError)是 JVM 认为无法通过 GC 解决内存不足时抛出的 Error(非 Exception)。完整 8 类,每类触发场景和排查手段不同。STEP 2 · 大白话
类比:OOM 像"仓库爆仓"——不同爆仓类型对应不同原因:
- 堆溢出:货物堆积太多(缓存无界、监听器未反注册)
- Metaspace:货品种类太多(动态代理、脚本引擎反复编译)
- DirectBuffer:装卸货区的临时堆(Netty ByteBuf 未释放)
- 线程过多:搬运工太多,都占着空间
关键是先看清是哪个区爆仓(看错误消息),才能对症下药。
STEP 3 · 底层
OOM 完整 8 类
| # | 类型 | 触发场景 | 排查手段 |
|---|---|---|---|
| 1 | java.lang.OutOfMemoryError: Java heap space | 缓存无界、集合未清、监听器未反注册、ThreadLocal 未 remove | 堆 dump + MAT |
| 2 | GC overhead limit exceeded | GC >98% 但回收 <2%,堆溢出前兆 | 堆 dump + GC 日志 |
| 3 | Metaspace | 动态代理(CGLIB/ByteBuddy)、脚本反复编译、热部署反复创建 ClassLoader | jcmd GC.class_histogram |
| 4 | Direct buffer memory | Netty ByteBuf 未 release、FileChannel.map 未 unmap | jcmd VM.native_memory、JFR jdk.DirectByteBuffer |
| 5 | unable to create new native thread | Thread 未复用、线程池无上限、ThreadLocal 泄漏导致线程膨胀 | 线程 dump(jstack) |
| 6 | Requested array size exceeds VM limit | 一次申请超大数组 | 检查代码数组大小 |
| 7 | Compressed class space | -XX:CompressedClassSpaceSize 满 | 调大压缩类空间 |
| 8 | unable to make continuation | Java 21+ Loom 协程溢出 | 检查虚拟线程配置 |
典型触发场景详解
堆溢出:- 静态集合持续增长(
static Map/static List无清理) - 缓存无 TTL、无淘汰策略
- 大对象一次性加载(
readAllBytes、readLines) - 监听器未反注册导致回调链泄漏
- ThreadLocal 未 remove 导致线程膨胀
- CGLIB/ByteBuddy 动态生成类
- Groovy/JavaScript 脚本引擎反复编译
- 热部署反复创建 ClassLoader
- 反射生成大量代理类
- Netty
ByteBuf未release() FileChannel.map()未unmap- NIO
ByteBuffer.allocateDirect未 GC 回收(JDK 8 需 PhantomReference 手动清理,JDK 9+ 用 Cleaner)
救命参数组合(生产必备)
# OOM 时自动 dump 堆(救命!)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/dumps/
# OOM 后退出,方便容器重启
-XX:+ExitOnOutOfMemoryError
# GC 日志(JDK 9+)
-Xlog:gc*
# 原生内存追踪(NMT)
-XX:NativeMemoryTracking=detail
# 堆限制
-XX:MaxDirectMemorySize=512m
-XX:MaxMetaspaceSize=512m
排查工具
运行时命令:jstat -gcutil:GC 频率与堆使用率1000 jcmd:堆信息GC.heap_info jcmd:NMT 原生内存VM.native_memory summary jmap -histo:live:对象直方图jstack:线程 dumpjcmd:类直方图(查 ClassLoader 泄漏)GC.class_histogram
- MAT(Eclipse Memory Analyzer):Dominator Tree、Leak Suspects、Shortest Path to GC Roots
- VisualVM
- JProfiler
- JFR(Java Flight Recorder):
jdk.GCPhasePause、jdk.GCHeapSummary、jdk.DirectByteBuffer - Arthas
dashboard - Prometheus + JVMExporter + Grafana
排查 SOP
- 先看 OOM 类型(异常消息,锁定问题区域)
- 打开 dump,MAT 看 Dominator Tree 找最大对象持有者
- Shortest Path to GC Roots:判断是不是被"不该引用的强引用"持有
- Metaspace 溢出:
GC.class_histogram看 ClassLoader 数量 - DirectBuffer 溢出:
VM.native_memory或 JFR 事件分析 - GC overhead:通常是堆溢出的前兆,先看堆 dump
解决矩阵
| 问题 | 短期处置 | 长期解决 |
|---|---|---|
| 堆溢出 | kill + 换实例、扩堆 | 修内存泄漏、优化分配 |
| Metaspace 溢出 | 调大 -XX:MaxMetaspaceSize | 排查 ClassLoader 泄漏 |
| DirectBuffer 溢出 | kill + 重启 | 显式释放、JDK 9+ Cleaner |
| GC overhead | 扩堆、换 GC 器 | 修泄漏、优化分配 |
| 线程过多 | kill 线程、扩容 | 线程池化、ThreadLocal 管理 |
STEP 4 · 简化
一句话总结:OOM 完整 8 类,先按错误消息锁定类型,再对症下药。救命参数+排查三件套是生产必备。记忆口诀:
- 堆 / Metaspace / Direct / 线程 / 数组 / 类空间 / 协程 = 7 类主战场
- GC overhead = 堆溢出前兆
- 救命参数:
HeapDumpOnOutOfMemoryError+ExitOnOutOfMemoryError+Xlog:gc* - 排查三件套:dump + 日志 + NMT
常见误区
"OOM 只有堆溢出"
完整 8 类,Metaspace / DirectBuffer / 线程 / 数组 / 协程 都要考虑
"GC overhead 是独立问题"
通常是堆溢出的前兆,先看堆 dump
"OOM 直接重启就好"
重启前必须先 dump,否则丢失现场,无法定位
"Netty ByteBuf 会自动释放"
必须显式
release(),JDK 9+ 有 Cleaner 兜底但会延迟"
-XX:MaxDirectMemorySize 是堆内存限制"它是堆外直接内存限制,与堆独立
延伸追问
Java heap space 和 GC overhead 有什么关系?
GC overhead 通常是堆溢出的前兆——GC 努力回收但效果差;先出现 GC overhead,随后堆溢出
如何判断是内存泄漏还是正常的堆使用?
多次 FullGC 后堆使用率仍不下降就是泄漏;MAT Dominator Tree 找"存活最多"的根对象;Shortest Path to GC Roots 看是否被"不该引用的强引用"持有
Metaspace 溢出如何定位 ClassLoader 泄漏?
jcmd GC.class_histogram 看 ClassLoader 数量;查热部署、脚本引擎、反射动态代理;用 VM.classloader_stats 事件DirectByteBuffer 泄漏怎么查?
JDK 9+ 用 Cleaner 自动回收;JDK 8 靠 PhantomReference;
jcmd VM.native_memory、JFR jdk.DirectByteBuffer;Netty 用 ByteBuf.refCnt()生产 OOM 的应急 SOP?
kill 前
-XX:+HeapDumpOnOutOfMemoryError 保证 dump;kill -15 而非 -9;保留 dump 做 MAT 分析;扩堆作为临时措施;根因排查优先修泄漏速查表
OOM 8 类:
1. Java heap space (最常见)
2. GC overhead (堆溢出前兆)
3. Metaspace (ClassLoader 泄漏)
4. Direct buffer memory (Netty 未 release)
5. unable to create new native thread
6. Requested array size exceeds VM limit
7. Compressed class space
8. unable to make continuation (Loom)
救命参数:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path
-XX:+ExitOnOutOfMemoryError
-Xlog:gc*
-XX:NativeMemoryTracking=detail
排查三件套:
1. 堆 dump + MAT (Dominator Tree / Shortest Path to GC Roots)
2. GC 日志 -Xlog:gc*
3. 原生内存 jcmd VM.native_memory (NMT)
Anki 候选卡片
Q: OOM 完整 8 类?
A: Java heap space / GC overhead / Metaspace / Direct buffer / native thread / array size / compressed class / continuation
Q: GC overhead limit exceeded 是什么?
A: GC >98% 但回收 <2%,通常是堆溢出的前兆
Q: OOM 救命参数?
A:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=... -XX:+ExitOnOutOfMemoryErrorQ: Metaspace 溢出怎么定位 ClassLoader 泄漏?
A:
jcmd GC.class_histogram、VM.classloader_stats 事件Q: Netty ByteBuf 内存泄漏怎么防?
A: 显式
release(),JDK 9+ Cleaner 兜底;jcmd VM.native_memory、JFR jdk.DirectByteBuffer关联题目
关联知识
OOM 完整 8 类,每类触发场景和排查手段不同;救命参数 HeapDump+Exit 是生产最重要排查手段
OOM 像仓库爆仓:堆=货物堆积、Metaspace=货品种类、DirectBuffer=临时堆、线程=搬运工
✦ 记 忆 口 诀 ✦
救命三参数 HeapDump+HeapDumpPath+Exit;排查三件套 堆 dump+GC 日志+原生内存
关键可视化
OOM 完整 8 类
flowchart TB O["OOM 8 类"] --> O1["1. Java heap space<br/>缓存无界、监听器未反注册、ThreadLocal 未 remove"] O --> O2["2. GC overhead limit exceeded<br/>GC>98% 但回收<2%<br/>堆溢出前兆"] O --> O3["3. Metaspace<br/>动态代理/脚本反复编译/热部署"] O --> O4["4. Direct buffer memory<br/>Netty ByteBuf 未 release<br/>FileChannel.map 未 unmap"] O --> O5["5. unable to create new native thread<br/>Thread 未复用/线程池无上限"] O --> O6["6. Requested array size exceeds VM limit<br/>一次申请超大数组"] O --> O7["7. Compressed class space<br/>压缩类空间满"] O --> O8["8. unable to make continuation<br/>Java 21+ Loom 协程溢出"]
救命参数组合
flowchart LR P["生产救命三参数"] --> P1["-XX:+HeapDumpOnOutOfMemoryError<br/>OOM 时自动 dump"] P --> P2["-XX:HeapDumpPath=/var/log<br/>dump 路径"] P --> P3["-XX:+ExitOnOutOfMemoryError<br/>OOM 后退出容器快速重启"] P1 -.-> D["MAT 分析 dump<br/>看支配树和泄漏点"] P3 -.-> R["配合 dump 保留现场"]
排查三件套
flowchart TB D["OOM 排查"] --> T1["堆 dump(MAT)<br/>看支配树、泄漏点<br/>-XX:+HeapDumpOnOutOfMemoryError"] D --> T2["GC 日志(-Xlog:gc*)<br/>看 GC 频率、STW、FullGC<br/>-Xlog:gc*:file=gc.log"] D --> T3["原生内存(jcmd VM.native_memory)<br/>看堆外内存占用<br/>jcmd VM.native_memory summary"]
知识关系
🔄 延伸(Extends)
暂无
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面