JVM垃圾收集器深度解析:G1与ZGC

在 Java 应用性能优化中,垃圾收集器的选择与调优是关键环节。随着应用规模的扩大和对响应时间要求的提高,传统的垃圾收集器(如 CMS)已难以满足现代应用的需求。G1 和 ZGC 作为现代垃圾收集器的代表,提供了更优的性能和更低的停顿时间。本文将深入解析 G1 和 ZGC 的工作原理、特性、参数优化及适用场景。

一、G1 垃圾收集器详解

1. 基本特性

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。其核心特点:

  • 高概率满足停顿时间要求:以极高概率满足 GC 停顿时间要求
  • 高吞吐量:同时具备高吞吐量性能特征
  • Region 内存布局:将 Java 堆划分为多个大小相等的独立区域(Region)
  • 最大 Region 数:JVM 最多可以有 2048 个 Region
  • Region 大小:一般 Region 大小等于堆大小除以 2048,如堆大小为 4096M,则 Region 大小为 2M

2. G1 内存布局

G1 保留了年轻代和老年代的概念,但不再是物理隔阂,它们都是(可以不连续)Region 的集合:

  • 默认年轻代占比:5%(堆大小为 4096M 时,年轻代约 200MB,对应约 100 个 Region)
  • 年轻代最大占比 :60%(可通过 XX:G1MaxNewSizePercent 调整)
  • Region 功能动态变化:一个 Region 可能之前是年轻代,回收后可能变成老年代

3. G1 工作原理

G1 垃圾收集器的运作过程分为四个阶段:

  1. 初始标记(Initial Mark,STW:暂停所有其他线程,记录 gc roots 直接能引用的对象,速度很快
  2. 并发标记(Concurrent Marking:遍历对象图做可达性分析,与 CMS 类似
  3. 最终标记(Remark,STW:重新标记,修正并发标记期间的变动
  4. 筛选回收(Cleanup,STW:根据用户期望的 GC 停顿时间制定回收计划

筛选回收阶段:G1 根据用户期望的 GC 停顿时间(-XX:MaxGCPauseMillis)制定回收计划,优先回收价值最大的 Region。例如,如果老年代有 1000 个 Region,但期望停顿时间只能回收 800 个,则只回收 800 个 Region。

4. G1 垃圾收集分类

类型 触发条件 说明
YoungGC Eden 区回收时间接近 -XX:MaxGCPauseMillis 不是 Eden 区满就触发,G1 会计算回收时间
MixedGC 老年代占用率达到 -XX:InitiatingHeapOccupancyPercent(默认 45%) 回收所有 Young 和部分 Old,避免 Full GC
Full GC 无法满足 MixedGC 条件 采用单线程进行标记、清理和压缩整理

5. G1 关键参数

参数 默认值 说明
-XX:+UseG1GC - 启用 G1 收集器
-XX:MaxGCPauseMillis 200 目标暂停时间(毫秒)
-XX:G1NewSizePercent 5 新生代初始占比
-XX:G1MaxNewSizePercent 60 新生代最大占比
-XX:InitiatingHeapOccupancyPercent 45 老年代占用率达到此值触发 MixedGC
-XX:G1MixedGCLiveThresholdPercent 85 Region 中存活对象低于此值才回收
-XX:G1MixedGCCountTarget 8 一次回收过程中做筛选回收的次数
-XX:G1HeapWastePercent 5 GC 过程中空出来的 Region 是否充足阈值

6. G1 优化建议

核心原则​:避免存活对象过多快速进入老年代,导致频繁触发 Mixed GC

  1. **合理设置 XX:MaxGCPauseMillis**:
    • 设置过低(如 20ms):可能导致每次回收的 Region 太少,收集速度跟不上分配速度
    • 设置合理(100-300ms):平衡停顿时间和收集效率
  2. 避免对象过早进入老年代
    • 调整 XX:MaxTenuringThreshold:减少对象晋升阈值
    • 例如,将默认 15 改为 5,让对象需要经过 5 次 Minor GC 才进入老年代
  3. 合理设置新生代大小
    • 避免年轻代过大(如超过 60%),导致存活对象过多
    • 例如,8G 内存的 JVM,设置 Xmn2G(2G 年轻代)

7. G1 适用场景

  1. 50% 以上的堆被存活对象占用
  2. 对象分配和晋升的速度变化非常大
  3. 垃圾回收时间特别长,超过 1 秒
  4. 8GB 以上的堆内存(建议值)
  5. 停顿时间要求 500ms 以内

典型案例:Kafka 等高并发消息系统,每秒处理几万甚至几十万消息,使用 G1 收集器,设置-XX:MaxGCPauseMillis=50ms,50ms 的停顿用户几乎无感知,系统可以一边处理业务一边收集垃圾。

二、ZGC 垃圾收集器详解

1. 基本特性

ZGC(Z Garbage Collector)是 JDK 11 中新加入的具有实验性质的低延迟垃圾收集器,其目标:

  • 支持 TB 量级的堆:满足未来十年内所有 Java 应用的需求
  • 最大 GC 停顿时间不超 10ms:比 CMS 更优
  • 停顿时间不随堆增大而增长:几十 G 堆停顿时间 10ms 以下,几百 G 甚至上 T 堆也是 10ms 以下
  • 不设分代(暂时):单代,没有分代概念

2. ZGC 内存布局

ZGC 的 Region 可以具有大、中、小三类容量:

Region 类型 容量 用途
小型 Region 2MB 小于 256KB 的对象
中型 Region 32MB 256KB-4MB 的对象
大型 Region 动态变化 4MB 或以上的大对象

大型 Region 特点:每个大型 Region 中只存放一个大对象,容量最小为 4MB,不会被重分配(因为复制大对象代价高)。

3. ZGC 核心技术

(1) 颜色指针(Colored Pointers)

ZGC 的核心设计之一,将 GC 信息保存在指针中,而非对象头:

  • 64 位指针结构
    • 18 位:预留给未来使用
    • 1 位:Finalizable 标识
    • 1 位:Remapped 标识
    • 1 位:Marked1 标识
    • 1 位:Marked0 标识
    • 42 位:对象地址(支持 4T 内存)

为什么有 2 个 mark 标记:每个 GC 周期开始时,交换使用的标记位,使上次 GC 周期中修正的已标记状态失效。

(2) 读屏障(Read Barrier)

ZGC 采用读屏障,而不是写屏障:

  • 工作原理:每次从堆中对象引用类型读取指针时,需要加上 Load Barriers
  • 作用:当对象被移动时,读屏障会自动修正指针

读屏障示意图:

复制代码
Object p = o  // 无读屏障
o.doSomething() // 无读屏障
obj.fieldB = 1 // 无读屏障

Object p = o  // 有读屏障
o.doSomething() // 有读屏障
obj.fieldB = 1 // 无读屏障

(3) NUMA-aware

ZGC 能自动感知 NUMA 架构并充分利用:

  • NUMA:Non Uniform Memory Access Architecture,每个 CPU 对应一块内存
  • 优势:提高内存访问效率,减少内存竞争

4. ZGC 工作过程

ZGC 的运作过程分为四个阶段:

  1. 并发标记(Concurrent Mark:遍历对象图做可达性分析,初始标记和最终标记有短暂 STW
  2. 并发预备重分配(Concurrent Prepare for Relocate:统计要清理的 Region,组成重分配集(Relocation Set)
  3. 并发重分配(Concurrent Relocate:将存活对象复制到新 Region,维护转发表(Forward Table)
  4. 并发重映射(Concurrent Remap:修正指向重分配集的引用

"自愈"能力:ZGC 的指针"自愈"能力,使用户线程访问重分配集中的对象时,自动转发到新对象。

5. ZGC 关键参数

参数 说明
-XX:+UnlockExperimentalVMOptions 解锁实验性参数
-XX:+UseZGC 启用 ZGC
-XX:ZCollectionInterval 定时触发 GC(默认不使用)
-XX:ZProactive 主动触发 GC(默认开启)
-XX:ZPageSizePolicy 设置 ZGC 的页大小策略

6. ZGC 触发时机

ZGC 有 4 种机制触发 GC:

  1. 定时触发 :默认不使用,可通过 ZCollectionInterval 配置
  2. 预热触发:最多三次,堆内存达到 10%、20%、30% 时触发
  3. 分配速率:基于正态分布统计,计算内存 99.9% 可能的最大分配速率
  4. 主动触发:默认开启,堆内存增长 10% 或超过 5 分钟触发

7. ZGC 问题与挑战

  1. 浮动垃圾
    • ZGC 的停顿时间在 10ms 以下,但执行时间远大于停顿时间
    • 期间产生的新对象无法回收,只能等到下次 GC
  2. 没有分代
    • 每次都需要进行全堆扫描
    • 导致"朝生夕死"的对象未能及时回收

解决方案:增大堆容量,使程序得到更多喘息时间,但这是治标不治本的方案。

8. ZGC 适用场景

  1. 超大内存应用(几百 GB 以上)
  2. 低延迟要求(停顿时间 <10ms)
  3. 高吞吐量应用(ZGC 吞吐量降低 15% 可接受)

三、G1 与 ZGC 对比

特性 G1 ZGC
目标 高吞吐 + 低停顿 低停顿(<10ms)
停顿时间 可预测,可设置 <10ms,与堆大小无关
内存模型 Region 划分,分代 Region 划分,单代
内存容量 适合 8GB-几百 GB 适合几百 GB-上 TB
GC 算法 复制 + 标记整理 标记整理
并发性 部分并发(筛选回收阶段 STW) 几乎完全并发
内存碎片 几乎无
适用场景 8GB 以上,停顿时间要求 500ms 内 超大内存,停顿时间要求 <10ms

四、如何选择垃圾收集器

1. 选择原则

内存大小 推荐收集器 说明
<100MB Serial 简单高效
单核,无停顿要求 Serial 无多线程开销
停顿时间 >1 秒 Parallel 高吞吐量
停顿时间 <1 秒 CMS/G1 低停顿
4GB 以下 Parallel 简单高效
4-8GB ParNew+CMS 平衡停顿与吞吐
8GB 以上 G1 适合大内存
几百 GB 以上 ZGC 低停顿,超大内存

2. 实际应用建议

  1. 中小型应用
    • 8GB 以下:使用 G1(JDK 9+ 默认收集器)
    • 8GB 以上:使用 G1
  2. 超大内存应用
    • 100GB 以上:考虑 ZGC(JDK 11+)
    • 500GB 以上:ZGC 是最佳选择
  3. 高并发低延迟系统
    • 停顿时间 <50ms:G1
    • 停顿时间 <10ms:ZGC

五、总结与建议

1. G1 核心优势

  • 可预测的停顿时间 :通过 XX:MaxGCPauseMillis 设置
  • 空间整合:使用复制算法,几乎无内存碎片
  • 适应大内存:适合 8GB 以上堆内存
  • 混合收集:YoungGC 和 MixedGC 减少 Full GC

2. ZGC 核心优势

  • 超低停顿:最大 GC 停顿时间 <10ms
  • 与堆大小无关:几十 GB 和上 TB 堆的停顿时间相同
  • 几乎完全并发:减少 STW 时间
  • 支持超大内存:适合 TB 级堆内存

3. 优化建议

  1. G1 优化
    • 设置合理的 XX:MaxGCPauseMillis(100-300ms)
    • 调整新生代大小,避免对象过早进入老年代
    • 使用 XX:G1MixedGCCountTarget=8 控制回收次数
  2. ZGC 优化
    • 增大堆容量,减少浮动垃圾
    • 启用 XX:ZProactive,避免频繁触发 Full GC
    • 监控 GC 日志,确认停顿时间是否符合预期

"G1 和 ZGC 代表了垃圾收集器的最新发展方向。G1 适合大多数大内存应用,ZGC 则为超大内存、超低延迟场景提供了最佳解决方案。理解它们的工作原理,能让你在 Java 应用性能优化的道路上走得更远。"

实战建议清单

问题类型 诊断方法 解决方案
Full GC 频繁 GC 日志分析 优化 G1 参数,增加 MixedGC 触发阈值
停顿时间长 监控停顿时间 选择 G1 或 ZGC,设置合理的停顿目标
大对象处理 分析大对象大小 设置 -XX:PretenureSizeThreshold,避免大对象进入老年代
超大内存应用 内存大小分析 选择 ZGC,设置 -XX:+UseZGC

最后提醒​:在实施 G1 或 ZGC 优化前,务必在测试环境验证效果。一个错误的 JVM 参数可能导致生产环境严重问题,而正确的优化能带来 10 倍性能提升。

"当你能读懂 G1 的工作原理、理解 ZGC 的创新设计、掌握优化技巧,你就真正掌握了 Java 应用的垃圾回收。从源码到执行,这是一条充满智慧的道路。"

相关推荐
qingwufeiyang_5302 小时前
JVM调优实战
jvm
u0109272716 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
m0_706653238 小时前
Python生成器(Generator)与Yield关键字:惰性求值之美
jvm·数据库·python
qq_423233909 小时前
实战:用Python开发一个简单的区块链
jvm·数据库·python
编程(变成)小辣鸡10 小时前
JVM、JRE和JDK 的关系
java·开发语言·jvm
hello 早上好10 小时前
02_JVM 架构模型中“栈式”与“寄存器式”指令集架构
jvm·架构
青槿吖11 小时前
【趣味图解】线程同步与通讯:从抢奶茶看透synchronized、ReentrantLock和wait/notify
java·开发语言·jvm·算法
难得的我们11 小时前
如何为开源Python项目做贡献?
jvm·数据库·python
焦糖玛奇朵婷11 小时前
就医陪诊小程序|从软件开发视角看实用度✨
java·大数据·jvm·算法·小程序