一篇文章让你通关java垃圾回收器

Java 垃圾回收器(Garbage Collector)详解

这篇文档讲的是 HotSpot JVM(Oracle/OpenJDK 系)里常见/主流的垃圾回收器。不同 GC 的目标不一样:有的追求吞吐,有的追求低延迟,有的追求“我只要不崩就行”。


1. 先把大框架捋清楚:GC 到底在管什么?

1.1 典型的堆分代(Generational Heap)

大多数 HotSpot GC 都默认走“分代”思路(ZGC / Shenandoah 也在逐步/已经支持分代能力):

  • Young(新生代)
    • Eden + S0/S1(Survivor)
    • 对象大多“朝生夕死”,这里回收频繁
  • Old(老年代)
    • 存活时间长的对象
    • 回收不频繁,但一次回收成本大
  • (可选)Humongous/大对象区域
    • G1 有 Humongous Region 的概念

1.2 GC 指标:你真正关心什么?

  • 吞吐(Throughput):业务线程时间 / 总时间(越高越好)
  • 延迟(Latency):STW(Stop-The-World)停顿时间(越低越好)
  • 内存占用(Footprint):为了低延迟可能要更多“冗余空间”
  • 可预测性(Predictability):停顿是否稳定、是否可控

你选 GC,其实是在做取舍:吞吐 vs 延迟 vs 内存


2. HotSpot 常见垃圾回收器总览

按“类型”粗暴分组:

2.1 串行 / 并行(以吞吐为核心)

  • Serial GC(串行):单线程 GC,简单粗暴
  • Parallel GC(吞吐优先)
    • Parallel Scavenge(新生代并行)
    • Parallel Old(老年代并行)

2.2 并发低延迟(以减少停顿为核心)

  • G1 GC:面向大堆的“平衡型”默认 GC(Java 9+ 默认)
  • ZGC:超低延迟、可扩展到超大堆(并发压缩)
  • Shenandoah:低延迟并发压缩(社区与部分发行版支持较强)

2.3 特殊用途

  • Epsilon GC:不回收(用来压测/性能对比/让你 OOM 得更快更确定)

3. Serial GC(-XX:+UseSerialGC)

3.1 特点

  • 单线程进行 Young/Old 回收
  • STW 停顿相对明显,但实现简单、额外开销小

3.2 适用场景

  • 单核/低核 CPU
  • 小堆、简单服务、工具型程序
  • 容器资源非常紧张,且延迟要求不高

3.3 常见参数

  • -XX:+UseSerialGC
  • -Xms -Xmx:堆大小
  • -Xmn-XX:NewRatio:新生代比例

4. Parallel GC(吞吐优先)

4.1 Parallel Scavenge + Parallel Old

  • 新生代:Parallel Scavenge
  • 老年代:Parallel Old
  • 典型目标:最大化吞吐(适合批处理、离线计算、吞吐型接口)

启用方式:

  • -XX:+UseParallelGC(通常会配套并行老年代)
  • 或显式:-XX:+UseParallelOldGC

4.2 优点

  • 多线程 GC,吞吐高
  • 参数体系成熟、稳定

4.3 缺点

  • STW 停顿可能比较长(尤其大堆/老年代回收)
  • “延迟敏感”业务会难受

4.4 关键参数(吞吐调参常用)

  • -XX:ParallelGCThreads=<n>:并行 GC 线程数
  • -XX:MaxGCPauseMillis=<ms>目标停顿(不是保证)
  • -XX:GCTimeRatio=<n>:GC 时间占比目标(吞吐优先时常用)
  • -XX:+UseAdaptiveSizePolicy:自适应调节(默认一般开启)

经验:吞吐型服务可以先让 JVM 自适应跑起来,再用 GC 日志看瓶颈在哪(是频繁 Young GC,还是 Old GC 太重)。


5. CMS(Concurrent Mark Sweep)——历史地位很高,但已经退出舞台

CMS 曾经是低延迟的代表(“并发标记清除”),但它的主要问题是:

  • 标记-清除导致碎片化(需要 Full GC 压缩救场)
  • 维护成本高

JDK 14 中 CMS 已被移除(-XX:+UseConcMarkSweepGC 直接不可用)。
如果你线上还在用 CMS,要么你 JDK 很老(8/11),要么你应该尽快迁移到 G1 / ZGC / Shenandoah


6. G1 GC(Garbage-First)

启用方式:

  • -XX:+UseG1GC

6.1 核心思想:Region + 预测停顿

G1 把整个堆切成很多 Region,不再严格固定“物理上的新生代/老年代连续空间”,而是用一组 Region 逻辑上组成 Young/Old。

它的目标:

  • 在可控的停顿目标(MaxGCPauseMillis)下,
  • 优先回收“垃圾最多(回收收益最大)”的 Region(Garbage-First)

6.2 G1 的回收阶段(简化版)

  • Young GC(STW):复制/清理年轻代 Region
  • 并发标记(Concurrent Marking):找出老年代活对象
  • Mixed GC(STW):在 Young GC 基础上,顺手回收一部分老年代 Region

6.3 优点

  • 相对可预测的停顿(不是绝对保证,但比纯并行/串行更可控)
  • 大堆场景表现好(常见 4G~几十 G 都能扛)
  • 默认 GC(Java 9+ 一般默认 G1)

6.4 缺点

  • 调参空间大,配置不当容易“看不懂”
  • 极端低延迟(比如 <10ms)和超大堆场景,ZGC/Shenandoah 往往更合适

6.5 关键参数(G1 常用)

  • -XX:MaxGCPauseMillis=<ms>:停顿目标(常用 50~200ms 取决于业务)
  • -XX:InitiatingHeapOccupancyPercent=<percent>:触发并发标记阈值
  • -XX:G1HeapRegionSize=<size>:Region 大小(一般让 JVM 自己选)
  • -XX:ConcGCThreads=<n>:并发标记线程数
  • -XX:ParallelGCThreads=<n>:STW 并行线程数

6.6 常见坑

  • 频繁 Mixed GC:老年代增长太快,或晋升压力过大
  • Humongous 对象问题:大对象直接占用多个 Region,容易造成回收收益低、堆碎片压力
    • 优化方向:减少大对象分配(例如大数组、拼接),或调大 Region、或改对象生命周期

7. ZGC(Z Garbage Collector)

启用方式:

  • -XX:+UseZGC

7.1 它想解决什么?

目标是 超低停顿,并且停顿时间对堆大小不敏感(你把堆从 8G 拉到 128G,它也尽量不爆炸)。

ZGC 的关键点:

  • 几乎所有重活都并发做,包括 并发整理/压缩(compaction)
  • 依赖 读屏障(read barrier) 等机制来保证并发移动对象时引用一致

7.2 分代 ZGC(Generational ZGC)

  • 在 JDK 21 引入“可选分代 ZGC”
  • 在后续版本中,ZGC 的分代模式逐步成为默认方向(并计划移除非分代模式)

对你来说的直觉收益:

  • 分代后 Young 对象回收更高效,整体吞吐和延迟一般会更好

7.3 优点

  • 极低停顿(很适合对延迟抖动敏感的服务:交易/广告竞价/实时推荐/在线游戏等)
  • 超大堆依然能保持较低暂停

7.4 缺点

  • 对 CPU 更“吃”(并发工作多)
  • 对内存空间有要求(低延迟往往意味着更需要“空间换时间”)
  • 不是所有平台/发行版的支持程度完全一致(但主流 OpenJDK/Oracle JDK 支持很好)

7.5 常见参数

  • -XX:+UseZGC
  • -Xms -Xmx:建议尽量相等,减少伸缩干扰
  • -XX:ConcGCThreads=<n>:并发线程
  • -XX:ZAllocationSpikeTolerance=<n> 等(偏高级,建议先看日志再动)

8. Shenandoah GC(低停顿并发压缩)

启用方式(视发行版/版本而定):

  • -XX:+UseShenandoahGC

8.1 特色

  • 和 ZGC 一样主打低停顿
  • 也支持并发整理/压缩
  • 同样依赖 barrier(屏障)来保证并发移动对象的正确性

8.2 优点

  • 停顿很短、对大堆友好
  • 在部分发行版(比如 Red Hat 系)生态很成熟

8.3 缺点

  • 不同发行版/平台差异更明显一些(“能用”不代表“每家都一样快”)
  • 调参资料和线上经验不如 G1 普及(但已经越来越多)

8.4 常见参数(示例)

  • -XX:+UseShenandoahGC
  • -XX:ShenandoahGCHeuristics=<adaptive|static|compact|...>:启发式策略
  • -XX:ShenandoahMinFreeThreshold=<percent>

9. Epsilon GC(不回收)

启用方式:

  • -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

9.1 它有什么用?

  • 性能基线:看看“没有 GC 干扰”的极限吞吐/延迟
  • 压测内存泄漏:你会更快、更确定地 OOM
  • 对比不同 GC 的开销

9.2 注意

线上别用(除非你明确知道你在干啥)。


10. 怎么选 GC?(实践导向)

10.1 先按业务目标选

  1. 吞吐优先(离线任务/批处理/日志分析)
  • Parallel GC 往往性价比很高
  1. 综合平衡(大多数 Web 服务、业务系统)
  • G1 通常是稳妥默认选择(尤其你不想折腾)
  1. 超低延迟(P99/P999 卡死就出事)
  • ZGC / Shenandoah 优先尝试
  • 配套要做:减少大对象、减少分配抖动、降低对象存活时间

10.2 你还得考虑:JDK 版本

  • 老项目还在 JDK 8:你可能还在 Parallel/CMS 时代(建议评估升级)
  • JDK 11/17/21:G1/ZGC/Shenandoah 选择空间更大,特性更成熟

11. 必会:GC 日志怎么看?

11.1 建议统一打开 GC 日志(JDK 9+)

-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags

JDK 8(老格式):

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

11.2 重点看哪些指标?

  • Young GC 频率、每次停顿
  • Old/Mixed/Full GC 发生原因与频率
  • 晋升失败(promotion failed)、to-space exhausted
  • 并发周期是否跟得上分配速率(并发标记来不及会触发更重的停顿)

12. 常见问题与定位思路(非常实用)

12.1 “GC 很频繁”通常意味着什么?

  • 堆太小 / 新生代太小
  • 对象分配太快(QPS、缓存、日志、拼接)
  • 大对象太多(byte[]、char[]、大 List/Map)

处理顺序建议:

  1. 先确认是不是内存泄漏(Old 一直涨、回收后不降)
  2. 再看是不是分配速率过高(Young 过密)
  3. 再考虑扩堆 / 调整新生代比例 / 优化对象创建

12.2 “Full GC 停顿很长”

  • G1:Mixed 来不及回收老年代,退化到 Full GC
  • Parallel:老年代回收天然 STW,很正常但可能超出 SLA
  • CMS(如果你还在用老 JDK):碎片化触发压缩

解决方向:

  • 减少老年代压力:缓存策略、对象生命周期、减少晋升
  • 增大堆 + 优化分配
  • 迁移更低延迟 GC(ZGC/Shenandoah)

12.3 “CPU 飙高但 GC 日志看起来还行”

  • 可能是并发 GC 在吃 CPU(ZGC/Shenandoah/G1 并发阶段)
  • 也可能是业务线程本身问题(锁竞争/IO/自旋)

做法:

  • async-profiler / JFR 看火焰图
  • 对照 GC 日志时间线,看 CPU 峰值是否和 GC 并发周期重合

13. 一句话总结(帮你记牢)

  • Serial:小堆/简单场景,能用但停顿明显
  • Parallel:吞吐怪兽,延迟不友好
  • G1:默认稳妥的“平衡型”选择,适配面广
  • ZGC:低延迟王者之一,适合大堆+低停顿
  • Shenandoah:低延迟另一条路线,部分发行版更强
  • Epsilon:不回收,用来对比/压测/找泄漏

14. 附:常用启用示例

14.1 G1(通用 Web 服务)

java -Xms4g -Xmx4g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=100 \
  -Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags \
  -jar app.jar

14.2 ZGC(低延迟)

java -Xms8g -Xmx8g \
  -XX:+UseZGC \
  -Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags \
  -jar app.jar

14.3 Parallel(吞吐优先)

java -Xms4g -Xmx4g \
  -XX:+UseParallelGC \
  -Xlog:gc*:file=gc.log:time,uptime,level,tags \
  -jar batch.jar

15. 推荐学习路线(不绕弯子)

  1. 先把 GC 日志看懂(你调参的“仪表盘”)
  2. 熟悉 G1(最常见)
  3. 再学 ZGC / Shenandoah(低延迟场景)
  4. 最后学“调优方法论”:先定位问题,再动参数,别上来就堆参数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值