← 返回面试专题

💥 OOM / Full GC:内存泄漏与 GC 调优排查

核心目标:区分“内存不足/泄漏/缓存膨胀/大对象”并用 heap dump 证据闭环

🧩 场景题题干(面试官常用表述)

线上现象:频繁 Full GC,RT 变慢,最终 OOM 重启。 你如何判断是内存泄漏还是正常负载? 如何定位到泄漏对象?如何修复并长期治理?
💡 一句话思路:先看 GC/堆使用趋势(是否“回收后仍上升”),再抓 heap dump 找“占用最大且一直增长”的对象链,最后回到代码与业务量解释根因。

📚 学习正文:从“现象”定位到“泄漏对象/配置不当/大对象”

交互图:OOM / Full GC 排查闭环(点步骤切换证据与动作)

目标是形成“证据闭环”:现象(GC/指标)→ 是谁(dump)→ 为什么(引用链/代码)→ 怎么治(短期止血 + 长期治理)。

1) 确认现象类型(先分桶,避免方向错)

  • 看报错信息:Java heap space / Metaspace / Direct buffer memory / GC overhead limit
  • 看 Full GC 频率:是“突然暴涨”(发布/流量)还是“持续恶化”(泄漏/数据膨胀)。
  • 第一口径:先明确目标:恢复可用(止血)还是定位根因(抓证据)。

1) 先止血:避免连续 Full GC 把服务拖死

止血优先级:摘流异常实例 → 降级/限流 → 回滚发布 → 扩容(谨慎,避免掩盖泄漏) 目标:让服务可用,争取时间抓证据(GC 日志/heap dump)。

2) 证据链:判断是不是“泄漏趋势”

3) 抓证据:GC 日志 + Heap Dump + 引用链

GC 日志回答“发生了什么”(频率/耗时/回收效果) Heap Dump 回答“是谁占用”(Top Consumers/Dominator Tree/GC Roots 引用链)

4) 常见 OOM 类型与定位方向

5) 修复与治理

短期:扩大堆/降低峰值/降级/回滚(视业务) 长期:缓存上限+淘汰、对象生命周期梳理、GC 指标告警、压测与回归、定期泄漏演练

🎯 面试题(建议学完上面正文再做)

1. Full GC 频繁一定是“内存泄漏”吗?简单

不一定。常见原因包括:

  • 堆设置过小:正常业务峰值就超出堆容量。
  • 短时流量峰值:瞬时大对象/大批量请求造成压力。
  • 缓存/队列膨胀:本地缓存、消息堆积、批处理集合未释放。
  • 真实泄漏:对象被错误引用长期存活,老年代不断上涨。
2. 如何快速判断“泄漏趋势”?你看哪些指标/证据?中等
  • 堆使用曲线:GC 后基线是否越来越高(锯齿整体抬升)。
  • 老年代占用:Old Gen 是否持续增长。
  • Full GC 收益:一次 Full GC 后释放很少,说明“可回收对象不多”。
⚠️ 关键点:“回收后仍上涨”比“堆很大/GC 很频繁”更能说明泄漏。
3. 如何抓 heap dump?怎么用它定位泄漏对象?困难
# 1) 找到 Java PID
ps -ef | grep java

# 2) 生成 heap dump(建议在业务低谷或先摘流)
jmap -dump:live,format=b,file=/tmp/heap.hprof <PID>
  • 找 Top Consumers:哪个类实例占用最大。
  • 看 Dominator Tree:谁在“支配”大量对象(谁不释放,下面都释放不了)。
  • 看引用链:从 GC Roots 到目标对象的链路,定位到具体缓存/Map/线程本地变量。
💡 面试表达:我会用 MAT/VisualVM 打开 dump,先找占用最多的类,再看支配树与引用链,最终回到代码的“谁持有引用”。
4. 常见 OOM 类型与排查方向?中等
  • Java heap space:堆对象过多/泄漏/大对象。
  • Metaspace:类加载过多、动态代理/脚本生成类、类卸载异常。
  • GC overhead limit exceeded:GC 花了大部分时间但回收很少,通常是堆压力或泄漏。
  • Direct buffer memory:NIO/Netty 直接内存未释放或上限过小。
5. 长期治理:你会怎么避免再次 Full GC/ OOM?困难
  • 配置基线:合理设置堆、元空间、直接内存,避免频繁扩缩容与抖动。
  • 缓存治理:本地缓存上限、淘汰策略、按业务键分片/分层。
  • 观测与告警:GC 次数/耗时、OldGen 占用、Full GC 频率告警。
  • 压测与回归:对大对象、批处理、导出等功能做专项压测。