前言

Full GC 频繁会导致接口抖动、服务卡顿甚至雪崩。面试中常见问题是:怎么判断是 GC 导致接口慢?Full GC 频繁怎么排查?JDK 8、17、21 的 GC 调优有什么区别?

Full GC 的影响

Full GC 通常会带来 Stop The World,应用线程暂停。表现为:

  • 接口 P99 周期性升高。
  • 监控出现请求尖刺。
  • 日志时间戳中间出现明显空洞。
  • CPU 升高但吞吐下降。
  • 老年代回收后很快又涨满。

排查流程

flowchart TD
    A[接口卡顿] --> B[查看GC监控]
    B --> C[确认Full GC频率和耗时]
    C --> D[分析GC日志]
    D --> E[老年代是否持续增长]
    E -->|是| F[堆Dump分析泄漏]
    E -->|否| G[对象晋升/大对象/参数不合理]
    G --> H[调整代码或JVM参数]
    F --> H

GC 日志怎么看

重点关注:

  • Young GC 次数和耗时。
  • Full GC 次数和耗时。
  • GC 前后堆使用量。
  • 老年代回收效果。
  • 晋升失败、分配失败。
  • Humongous Object。
  • Metaspace 使用量。

如果 Full GC 后老年代仍然很高,说明存活对象多,要怀疑内存泄漏或缓存过大。如果 Full GC 后能降下来,但很快又涨上去,说明对象分配速率过高或晋升过快。

JDK 8、17、21 的差异

版本 默认或常见 GC 排查重点
JDK 8 Parallel GC,常用 CMS/G1 CMS 碎片、晋升失败、永久代已变元空间
JDK 17 G1 常用 Region、Mixed GC、Humongous Object
JDK 21 G1 默认,ZGC 更成熟 低延迟场景可评估 ZGC,关注内存占用和吞吐权衡

JDK 17 和 21 更推荐使用统一日志参数 -Xlog:gc*,GC 日志格式和 JDK 8 不同。面试时不要把 JDK 8 的 CMS 经验直接套到 JDK 17/21。

常见根因

老年代持续增长

常见原因:

  • 本地缓存无上限。
  • 静态集合持有对象。
  • ThreadLocal 未清理。
  • 监听器、回调、定时任务持有对象。
  • 大批量任务一次性加载数据。

处理方式是分析堆 Dump,而不是盲目加内存。

对象晋升过快

如果新生代太小,或者对象存活时间略长,就会快速晋升到老年代。

常见场景:

  • 批量导入。
  • 报表生成。
  • 大列表查询。
  • MQ 批量消费。

优化方式:

  • 控制批量大小。
  • 流式处理。
  • 减少临时对象。
  • 调整新生代比例或 G1 目标停顿时间。

大对象分配

大对象容易直接进入老年代或 G1 Humongous 区域。

常见对象:

  • 大数组。
  • 大字符串。
  • 大 JSON。
  • 大 Excel。
  • 大图片或文件字节数组。

处理方式是避免一次性加载,改成流式、分片或临时文件。

GC 参数不合理

常见问题:

  • 堆设置过小,导致频繁 GC。
  • 堆设置过大,单次 GC 停顿过长。
  • 容器内存限制与 JVM 参数不匹配。
  • JDK 升级后沿用旧 GC 参数。

生产止血

  • 临时扩容实例,降低单实例流量。
  • 降低批处理并发和批量大小。
  • 关闭异常报表、导出、补偿任务。
  • 清理异常本地缓存。
  • 对问题接口限流。
  • 回滚最近发布。

如果是内存泄漏,扩容只能延迟故障再次发生,不能根治。

调优建议

  • 先优化代码和对象生命周期,再调 JVM 参数。
  • 建立 GC 基线,关注 Full GC 次数和 P99。
  • 大批量任务不要和核心在线接口混部署。
  • JDK 17/21 使用 G1 时重点看 Region、Mixed GC 和大对象。
  • 对低延迟系统评估 ZGC,但要压测吞吐和内存成本。

高频面试题

Full GC 频繁怎么排查?

先看 GC 日志确认频率、耗时和回收效果。如果 Full GC 后老年代不下降,分析堆 Dump 找泄漏;如果能下降但很快上涨,排查对象分配速率、大对象和批处理。

JDK 8 和 JDK 17 的 GC 排查有什么不同?

JDK 8 常见 CMS/Parallel/G1,日志参数和问题类型不同。JDK 17 常用 G1,使用统一日志,重点关注 Region、Mixed GC、Humongous Object 和容器内存适配。

调大堆能解决 Full GC 吗?

只能缓解部分容量不足问题。如果是内存泄漏或大对象设计问题,调大堆会让单次 GC 更慢,故障仍会复现。

总结

Full GC 排查要看趋势和回收效果。面试时要讲清楚 GC 日志、堆 Dump、对象生命周期、JDK 版本差异和生产止血手段。