外观
一句话答案
OOM 按区域分:堆溢出(对象过多/泄漏)、栈溢出(递归过深)、元空间溢出(类过多)、直接内存溢出(NIO ByteBuffer)。
核心要点
各区域的 OOM 情况:
| 区域 | 会不会 OOM | 典型错误 | 常见原因 |
|---|---|---|---|
| 堆 | ✅ 会 | java.lang.OutOfMemoryError: Java heap space | 内存泄漏、对象太多、堆设置太小 |
| 方法区/Metaspace | ✅ 会 | OutOfMemoryError: Metaspace | 动态生成大量类(CGLIB / Groovy 脚本),类卸载缓慢 |
| 虚拟机栈 | ✅ 会(栈溢出) | StackOverflowError | 无限递归;线程过多时 OutOfMemoryError: unable to create new native thread |
| 本地方法栈 | ✅ 会 | StackOverflowError | 同虚拟机栈 |
| 程序计数器 | ❌ 不会 | — | 唯一不会 OOM 的区域 |
线上排查 OOM 步骤:
第一步:获取 Heap Dump
bash
# 方式1:JVM 启动参数配置(推荐,OOM 时自动 dump)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/heapdump.hprof
# 方式2:手动 dump(进程仍在运行时)
jmap -dump:live,format=b,file=heapdump.hprof <pid>第二步:分析 Heap Dump
- 工具:MAT(Memory Analyzer Tool) 或 VisualVM
- 查找:Retained Heap(保留堆大小)最大的对象
- 定位:Dominator Tree(支配树)找出谁持有大量内存
第三步:结合代码定位根因
- 常见原因1:内存泄漏(如静态 Map 不断 put 却不 remove,或未关闭的连接/流)
- 常见原因2:缓存没有大小上限(如本地缓存无 eviction 策略)
- 常见原因3:大批量数据一次性加载(分页查询改成
SELECT *) - 常见原因4:Metaspace OOM:检查是否有大量动态类生成(用
-XX:+TraceClassLoading)
辅助工具:
bash
jstat -gcutil <pid> 1000 # 每秒输出 GC 统计,观察堆区使用率
jinfo -flag MaxMetaspaceSize <pid> # 查看 Metaspace 上限
Arthas: memory 命令实时查看各区域内存使用追问与易错
追问方向:
- Java heap space 和 GC overhead limit exceeded 区别?(后者是 GC 时间过长)
- 元空间 OOM 一般什么原因?(动态代理/CGLIB 生成大量类)
- 线上出了 OOM 你怎么排查的?(dump→MAT→Dominator Tree→泄漏路径)
易错点:
- ❌ "OOM 就是堆溢出"——还有栈溢出/元空间/直接内存 OOM
- ❌ 不配置 HeapDumpOnOOM——OOM 只发生一次,错过现场很难复现
💡 记忆锚点
OOM四大灾区:堆溢出(对象太多/泄漏)、栈溢出(递归无底洞)、Metaspace溢出(动态类爆炸)、直接内存溢出(NIO)。排查三步走:HeapDump抓现场、MAT查支配树、顺藤摸瓜找代码。救命参数:-XX:+HeapDumpOnOutOfMemoryError,不配就是丢掉黑匣子。