极高 进阶
OOM类型与排查#
一句话答案#
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
# 方式1:JVM 启动参数配置(推荐,OOM 时自动 dump)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/heapdump.hprof
# 方式2:手动 dump(进程仍在运行时)
jmap -dump:live,format=b,file=heapdump.hprof <pid>bash第二步:分析 Heap Dump
- 工具:MAT(Memory Analyzer Tool) 或 VisualVM
- 查找:Retained Heap(保留堆大小)最大的对象
- 定位:Dominator Tree(支配树)找出谁持有大量内存
第三步:结合代码定位根因
- 常见原因1:内存泄漏(如静态 Map 不断 put 却不 remove,或未关闭的连接/流)
- 常见原因2:缓存没有大小上限(如本地缓存无 eviction 策略)
- 常见原因3:大批量数据一次性加载(分页查询改成
SELECT *) - 常见原因4:Metaspace OOM:检查是否有大量动态类生成(用
-XX:+TraceClassLoading)
辅助工具:
jstat -gcutil <pid> 1000 # 每秒输出 GC 统计,观察堆区使用率
jinfo -flag MaxMetaspaceSize <pid> # 查看 Metaspace 上限
Arthas: memory 命令实时查看各区域内存使用bash面试回答(2分钟版)
OOM 按发生区域主要分四种:第一是堆溢出 Java heap space,最常见,原因是内存泄漏、大批量数据一次性加载或堆设置过小;第二是 Metaspace 溢出,常见于 CGLIB、Groovy 等动态生成大量类的场景;第三是栈溢出 StackOverflowError,无限递归导致栈帧超出 -Xss 限制;第四是直接内存溢出,NIO 的 DirectByteBuffer 分配超出 -XX:MaxDirectMemorySize。排查 OOM 我有一套标准流程:首先必须在启动参数中配置 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath,OOM 发生时自动生成堆转储文件,这是最关键的现场证据。拿到 hprof 文件后用 MAT 打开,通过 Dominator Tree 找到占用内存最大的对象,再通过 GC Roots 引用链分析定位是哪段代码持有了不该持有的引用。辅助手段是 jstat -gcutil 观察各区使用率和 GC 频率,Arthas 的 memory 命令实时监控。常见根因包括静态集合只 put 不 remove、连接和流未关闭、本地缓存没有 eviction 策略等。
追问与易错
追问方向:
- “Java heap space 和 GC overhead limit exceeded 区别?”→ 后者是 GC 时间过长
- “元空间 OOM 一般什么原因?”→ 动态代理/CGLIB 生成大量类
- 线上出了 OOM 你怎么排查的?(dump→MAT→Dominator Tree→泄漏路径)
易错点:
- ❌ “OOM 就是堆溢出”——还有栈溢出/元空间/直接内存 OOM
- ❌ 不配置 HeapDumpOnOOM——OOM 只发生一次,错过现场很难复现