🎬 怎么引出
「JVM 我处理过线上 OOM。一次是堆 OOM — 报表导出一次性查了 50 万条数据全放内存,堆撑爆。我用 jmap dump 堆快照,MAT 打开看支配树(Dominator Tree),定位到一个 ArrayList 占了 80% 内存,改成流式处理解决。还有一次是 Metaspace OOM — 动态生成的代理类太多没释放。」JVM + 真实排查故事,高级岗位加分项。
1

JVM 内存区域

区域存什么线程共享OOM 类型
堆(Heap)对象实例、数组共享java.lang.OutOfMemoryError: Java heap space
方法区/元空间(Metaspace)类信息、常量池、静态变量共享Metaspace(JDK8+)
虚拟机栈栈帧(局部变量、操作数栈)私有StackOverflowError
本地方法栈Native 方法私有StackOverflowError
程序计数器当前指令地址私有不会 OOM
直接内存NIO Buffer 等-Direct buffer memory
JDK 版本区别: JDK7 前永久代(PermGen,堆的一部分);JDK8+元空间(Metaspace,用本地内存,不在堆里)。这是高频考点,很多老资料还讲 PermGen。
2

GC 垃圾回收

🧠 分代收集
堆分新生代(Young)和老年代(Old)。新生代又分 Eden + 2 个 Survivor(S0/S1)。
对象先在 Eden 分配 → Minor GC 存活的进 Survivor → 经过多次 GC 还活着进老年代。
Minor GC(新生代,频繁快) / Major GC(老年代) / Full GC(全堆,慢,STW)。
📋 GC 算法
  • 标记-清除: 标记存活,清除其余。产生碎片
  • 标记-复制: 存活对象复制到另一半。新生代用(Survivor)
  • 标记-整理: 标记后整理到一端。老年代用
  • 分代收集: 新生代用复制,老年代用整理
📋 垃圾收集器
收集器特点适用
Serial单线程,STW客户端/小应用
Parallel(默认 JDK8)多线程,吞吐量优先JDK8 默认
CMS并发,低停顿(已废弃 JDK9+)对延迟敏感
G1(默认 JDK9+)分 Region,可预测停顿大堆,现代首选
ZGC/Shenandoah超低停顿(<10ms)超大堆,JDK11+
判断对象死亡:引用计数法(循环引用问题,Java 不用);② 可达性分析(GC Roots 到对象有路径就活着)。四种引用: 强(永不回收)→ 软(内存不足回收)→ 弱(下次 GC 回收)→ 虚(随时回收)。
🔑 GC Roots 有哪些(面试背全)
GC Root具体是什么泄漏概率泄漏场景
① 栈局部变量方法里正在用的对象极低方法结束自动出栈断开
② 静态变量static Map cache极高只 put 不 remove → 无限增长 → OOM
③ 活跃线程线程本身是 Root极高ThreadLocal 没 remove + 线程池复用
④ 常量引用static final较少见
⑤ 同步锁synchronized 持有的对象锁没释放 → 关联对象不回收
⑥ JNI 引用native 代码持有的 Java 对象native 忘释放
🤔 普通变量会泄漏吗? 会,但很容易被 GC 自动回收。局部变量方法结束自动断开 → 安全。static/ThreadLocal 永久连着 Root → 必须手动 remove核心规律:变量本身不泄漏,泄漏的是"引用链连着 GC Root 断不掉"。
3

OOM 类型

OOM 信息原因排查
Java heap space堆内存不够 / 内存泄漏jmap + MAT
GC overhead limitGC 花太多时间回收太少堆泄漏前兆
Metaspace类元数据太多(动态代理)-XX:MaxMetaspaceSize
Direct buffer memoryNIO 直接内存不够-XX:MaxDirectMemorySize
unable to create new native thread线程数太多(系统级)查线程泄漏
StackOverflowError栈溢出(死递归)看堆栈深度
4

OOM 排查工具

📋 排查流程
# 1. 找到占用高的 Java 进程
jps -lvm
top -c  # 找 Java 进程 PID

# 2. 看堆内存概况
jmap -heap <PID>

# 3. 看对象统计(哪些对象占得多)
jmap -histo:live <PID> | head -20

# 4. dump 堆快照(生产慎用,会 STW)
jmap -dump:format=b,file=heap.hprof <PID>

# 5. MAT 打开 heap.hprof
# - Dominator Tree(支配树):谁占了最多内存
# - Leak Suspects(泄漏嫌疑):自动分析
# - 找到 GC Root 引用链

# 线程问题用 jstack
jstack <PID> > thread.txt  # dump 线程,看死锁/阻塞
⚠️ 生产 dump 的坑

jmap -dump 会触发 Full GC(STW,暂停所有应用)。生产环境直接 dump 可能导致服务卡顿几十秒。正确做法: ① 提前配 -XX:+HeapDumpOnOutOfMemoryError(OOM 时自动 dump);② 用阿里 arthas 的 heapdump(影响小);③ 从节点 dump,不在主节点。

5

GC 调优

常见参数: -Xms 初始堆, -Xmx 最大堆(建议 Xms=Xmx 避免动态扩容), -Xmn 新生代, -XX:MetaspaceSize 元空间, -XX:+UseG1GC 用 G1, -XX:MaxGCPauseMillis=200 目标停顿时间。原则:Xms=Xmx,生产用 G1,别过度调优。
调优时机: 不要为调优而调优。 只在:① 频繁 Full GC;② GC 停顿影响响应;③ OOM 频发。否则默认参数(JDK9+ G1)已经很合理。先看 GC 日志(-Xlog:gc*)找问题。
6

高频追问

1JVM 内存区域有哪些? 必问
堆(对象)、方法区/元空间(JDK8+ 元空间,类信息)、虚拟机栈(栈帧)、本地方法栈、程序计数器、直接内存。堆和元空间共享,栈和计数器线程私有。重点:JDK8 后 PermGen 改 Metaspace(本地内存)。
2怎么判断对象可以被回收? 高频
可达性分析:从 GC Roots 出发,有路径可达就活着,不可达就回收。GC Roots:栈局部变量、静态变量、JNI 引用、活动线程。引用计数有循环引用问题,Java 不用。四种引用:强→软→弱→虚。
3线上 OOM 怎么排查? 高频
① 提前配 -XX:+HeapDumpOnOutOfMemoryError 自动 dump;② 或用 jmap -dump(注意 STW);③ MAT 打开 hprof:看 Dominator Tree 找大对象,Leak Suspects 自动分析,找 GC Root 引用链;④ 定位是泄漏(对象一直不释放)还是溢出(瞬时数据太大);⑤ 泄漏修代码,溢出加限流/分页/流式处理。
4G1 和 CMS 区别? 中等
CMS: 老年代并发收集器,低停顿,但有碎片(标记清除)、并发模式失败风险,JDK9 废弃。G1: 分 Region,可预测停顿时间(-XX:MaxGCPauseMillis),适合大堆,JDK9+ 默认。G1 整体更优,是现代首选。
5Minor GC 和 Full GC 区别? 基础
Minor GC: 清新生代(Eden+S0),频繁、快、STW 短。Full GC: 清整个堆(新生代+老年代),慢、STW 长、要尽量避免。Full GC 频繁说明:老年代不够、内存泄漏、大对象直接进老年代。
6堆参数怎么设置? 中等
-Xms 初始堆 = -Xmx 最大堆(避免动态扩容抖动)。一般服务器留一半给 OS 和堆外。新生代 -Xmn 一般堆的 1/3~1/2。生产 JDK9+ 直接用 G1(-XX:+UseG1GC),设 -XX:MaxGCPauseMillis=200别过度调优,默认 G1 很合理。
7

30 秒陈述

🎤 30 秒版
「JVM 我处理过线上 OOM。报表导出一次查 50 万条全放内存堆撑爆,我用 jmap dump + MAT 看 Dominator Tree,定位 ArrayList 占 80%,改流式处理解决。JVM 内存我清楚:堆(对象)+ 元空间(JDK8+,类信息)+ 栈 + 计数器。GC 分代,新生代用复制算法老年代用整理,G1 分 Region 可预测停顿。生产配 HeapDumpOnOutOfMemoryError 自动 dump,别等出事手动 jmap(STW)。

面试亮点专题 16 — JVM 内存与 OOM 排查

内存区域 · 分代 GC · G1/CMS · OOM 类型 · jmap/MAT 排查 · GC 调优

← 返回首页