JVM GC → 内存泄漏 → OOM 排查 追问链#
追问路径#
Q: JVM的垃圾回收是怎么工作的?
→ 可达性分析(GC Roots出发标记存活对象) + 分代回收(Young/Old) + 收集器(Serial/Parallel/CMS/G1/ZGC)
Q: 什么情况下会触发Full GC?
→ 老年代满 / 元空间不足 / System.gc() / 晋升担保失败 / CMS并发失败退化Serial Old
Q: Full GC频繁怎么排查?
→ jstat -gcutil 看GC频率和回收率;正常Young GC<50ms、Full GC<200ms
├─ Q: 常见的内存泄漏场景有哪些?
│ → ThreadLocal未remove / 静态集合无边界增长 / 未关闭的连接和流 / 监听器未注销 / 内部类持有外部引用
│ Q: OOM怎么定位?
│ → -XX:+HeapDumpOnOutOfMemoryError自动dump + MAT分析Dominator Tree找大对象
│ Q: 线上不能重启怎么办?
│ → Arthas heapdump在线导出(不重启) / jmap -dump:live(会触发Full GC慎用) / dashboard看实时内存
│ Q: 如果是堆外内存泄漏呢?
│ → NMT(NativeMemoryTracking): -XX:NativeMemoryTracking=detail + jcmd VM.native_memory;Netty的ByteBuf泄漏用-Dio.netty.leakDetection.level=PARANOID
└─ Q: 容器环境下JVM内存怎么配?
→ 容器内存限制 ≠ JVM可用内存;-Xmx建议设为容器内存的70%,预留给线程栈/元空间/堆外
Q: JDK8和JDK17在容器感知上有什么区别?
→ JDK8u191+才能正确识别cgroup限制;JDK17原生支持cgroup v2,默认MaxRAMPercentage=25%需要调大plaintext涉及知识点#
- 垃圾回收算法 — 标记清除/复制/标记整理
- G1收集器原理 — Region化/Mixed GC/可预测停顿
- 堆内存分代模型 — Eden/Survivor/Old的晋升机制
- OOM类型与排查 — 堆/栈/元空间/直接内存4种OOM
- ThreadLocal原理与内存泄漏 — 弱引用Entry与remove必要性
- Full-GC频繁排查 — 生产排查的标准SOP
- Arthas使用实战 — 在线诊断神器
- JVM常用调优参数 — Xmx/Xms/GC相关参数
- GC调优实战 — 调优目标与方法
- 容器环境JVM内存陷阱 — cgroup感知与内存配置
- 引用类型(强软弱虚) — 4种引用与GC行为
核心串联逻辑#
- GC基础:Young GC回收Eden区(复制算法,STW通常<50ms),Full GC回收整堆(目标<200ms)
- Full GC频繁 = 老年代回收效率低:每次Full GC回收很少空间 → 说明有对象无法回收但持续增长 → 内存泄漏
- 排查路径:
jstat -gcutil pid 1000看Old区占比和GC后回收率 → dump堆 → MAT的Dominator Tree定位 - 堆外泄漏:堆正常但进程RSS持续增长 → NativeMemoryTracking追踪;Netty场景用leak detection
- 容器陷阱:-Xmx=容器limit会OOM Killer(没给非堆预留空间),Xmx ≤ 容器内存 × 0.7
- 代码示例:
bash# 一键排查GC状态 jstat -gcutil <pid> 1000 5 # 输出: S0 S1 E O M YGC YGCT FGC FGCT GCT # 关注: O(老年代)>90%且FGC频繁 → 可能泄漏
面试回答串联#
30秒速答#
“Full GC频繁通常是内存泄漏导致老年代持续增长。排查路径:jstat确认GC模式,dump堆用MAT分析Dominator Tree找泄漏对象。常见原因有ThreadLocal未remove、静态Map无上限。生产配HeapDumpOnOOM保证第一现场。“
2分钟展开答#
“JVM垃圾回收基于可达性分析+分代回收。Young GC用复制算法回收Eden,STW通常不到50ms;Full GC回收整堆目标控制在200ms内。Full GC频繁说明老年代空间回收效率低——每次GC释放很少但对象持续涌入,典型的内存泄漏信号。我的排查流程:先用jstat -gcutil看Old区占比和Full GC后的回收率,如果每次回收很少说明有泄漏。然后HeapDumpOnOOM自动dump,或用Arthas的heapdump在线导出不需要重启。MAT打开后看Dominator Tree找到占内存最大的对象链,常见根因是ThreadLocal用完没remove(线程池复用线程导致累积)、静态HashMap无上限增长、数据库连接未关闭。如果是堆外内存泄漏(堆正常但进程RSS持续增长),用NativeMemoryTracking追踪,Netty场景开启leak detection。容器环境要注意Xmx不能等于容器limit,要预留30%给线程栈、元空间和堆外内存,否则会被OOM Killer杀掉。“
相关追问链#
- 线程池-Spring异步-MQ消费追问链 — ThreadLocal在线程池中的泄漏问题
- 进程线程-调度-上下文切换-协程追问链 — 线程栈OOM与线程数的关系
- 项目经验-技术选型-问题排查追问链 — OOM排查是典型的线上问题案例