面试知识库
极高 进阶

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同虚拟机栈
直接内存(非运行时数据区)✅ 会OutOfMemoryError: Direct buffer memoryNIO ByteBuffer.allocateDirect / Netty 堆外缓冲超过 -XX:MaxDirectMemorySize 或未释放
程序计数器❌ 不会—唯一不会 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:检查是否有大量动态类生成(JDK 8 用 -XX:+TraceClassLoading;JDK 9+ 用 -Xlog:class+load=info,该旧参数 JDK 17 起已不被识别)

辅助工具:

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 区别?”→ 前者是堆放不下新对象;后者是 JVM 花了过多时间做 GC 却几乎回收不出内存(Parallel GC 默认阈值:超过 98% 时间在 GC、回收不到 2% 的堆),本质也是堆快满了
  • “元空间 OOM 一般什么原因?”→ 动态代理/CGLIB/Groovy 等动态生成大量类,或类加载器泄漏导致类无法卸载;MaxMetaspaceSize 设得过小也会触发
  • “线上出了 OOM 你怎么排查的?”→ 先看报错类型确定区域;堆 OOM 用 HeapDumpOnOutOfMemoryError 自动导出的 hprof(或 jmap 手动 dump)→ MAT 打开看 Dominator Tree 找 Retained Heap 最大的对象 → 查到 GC Roots 的引用链定位泄漏代码 → 修复后压测验证

易错点:

  • ❌ “OOM 就是堆溢出”——还有栈溢出/元空间/直接内存 OOM
  • ❌ 不配置 HeapDumpOnOOM——OOM 只发生一次,错过现场很难复现