文章目录

Java 垃圾回收算法:G1与ZGC的停顿时间对比

发布于 2026-04-15 12:20:34 · 浏览 28 次 · 评论 0 条

Java 垃圾回收算法:G1与ZGC的停顿时间对比

高并发、低延迟是现代Java应用的核心诉求。在众多垃圾回收器(GC)中,G1(Garbage-First)长期作为JDK 8以来的默认选择,而ZGC(Z Garbage Collector)则是后来居上的低延迟新星。深入理解两者在停顿时间上的底层差异,是进行JVM调优的关键。


1. G1 的停顿机制:基于“复制”的权衡

G1 将堆内存划分为多个大小相等的独立区域。它通过追踪每个区域的垃圾积压程度,优先回收垃圾最多的区域,这也是“Garbage-First”名字的由来。

核心逻辑:G1 的垃圾回收过程主要包括“标记”和“复制”两个阶段。为了整理碎片,G1 必须将存活对象从一个 Region 复制到另一个 Region。这个过程涉及内存指针的修改,必须暂停所有应用线程,即产生“Stop-The-World”(STW)。

以下流程描述了 G1 在面对高分配速率时的处理过程:

graph LR A["应用线程: 持续分配对象"] --> B["Eden Region: 逐渐填满"] B -- 触发 GC --> C["STW 阶段: 初始标记"] C -- 并发运行 --> D["并发标记: 找出存活对象"] D -- 再次 STW --> E["最终混合回收: 复制存活对象"] E -- 恢复运行 --> A

停顿时间特征
G1 允许用户设定预期的最大停顿时间目标(例如 -XX:MaxGCPauseMillis=200)。JVM 会根据历史数据,计算出在规定时间内能回收多少个 Region,从而控制停顿。

局限性:当对象存活率很高(即需要复制大量对象)或者内存碎片严重时,为了达到回收目标,G1 可能被迫进行更长时间的 STW,甚至退化为 Serial GC 进行全量压缩,导致停顿时间剧烈抖动。


2. ZGC 的停顿机制:基于“染色指针”的并发

ZGC 的设计目标是将停顿时间控制在 10 毫秒以内(JDK 17 以后甚至能达到亚毫秒级)。它通过彻底的并发化设计,几乎消除了将堆内存冻结进行对象移动的必要。

核心技术:ZGC 引入了读屏障染色指针

  • 染色指针:利用 64 位操作系统中指针的高位几位(Linux/x64-64 使用第 42-47 位)来存储对象的状态(如 Finalizable、Remapped、Moved 等)。
  • 多重映射:将同一块物理内存映射到虚拟内存的三个不同地址视图,从而在应用线程访问对象时,通过读屏障自动修正指针,无需等待 GC 线程完成所有更新。

停顿时间特征
ZGC 的停顿仅发生在 GC 的初始标记和重新标记阶段,这两个阶段只需要遍历 GC Roots(如线程栈、静态变量),不涉及对象移动或大规模堆扫描。

其停顿时间公式近似为:
$$T_{ZGC\_Pause} \propto \text{GC\_Roots\_Count}$$

这意味着,无论堆内存是 4GB 还是 4TB,只要 GC Roots 的数量保持稳定,ZGC 的停顿时间就几乎恒定。


3. 核心指标对比

为了更直观地展示两者在面对不同场景下的表现,以下是关键维度的对比。

维度 G1 GC ZGC
设计目标 平衡吞吐量与延迟 极致低延迟,停顿 < 10ms
停顿时间稳定性 波动较大,随存活数据量增加而增加 极度稳定,不随堆大小增加而增加
堆内存上限 通常建议不超过 32GB - 64GB 支持 16TB (仅受限于硬件)
对象移动 STW 期间物理复制 并发重定位,利用读屏障修正指针
CPU 开销 较低 较高(读屏障带来的额外 CPU 消耗)
适用场景 通用型服务器应用,对吞吐量有要求 超大堆内存、对延迟极其敏感的系统

4. 实战:如何从 G1 切换到 ZGC

如果你的应用受困于 G1 的偶发长停顿,且运行在 JDK 11 或更高版本(建议 JDK 17+),可以按照以下步骤尝试切换到 ZGC。

4.1 验证运行环境

检查当前 JDK 版本。ZGC 在 JDK 15 之前是实验性质的,需要解锁实验特性。

在终端执行以下命令:

java -version

如果版本低于 11,必须先升级 JDK。

4.2 调整启动参数

移除 G1 相关的配置参数,如 -XX:+UseG1GC, -XX:MaxGCPauseMillis, -XX:ParallelGCThreads 等。

添加 ZGC 的开启参数。ZGC 的配置极其简单,通常只需开启开关即可,它会自动调整。

修改启动脚本(如 setenv.shapplication.conf):

# 开启 ZGC
-XX:+UseZGC

4.3 设置堆大小与日志

设置初始堆大小 (-Xms) 和最大堆大小 (-Xmx)。为了避免动态调整堆大小带来的性能损耗,建议将两者设为相同值。

-Xms8g -Xmx8g

开启 GC 日志以监控效果。ZGC 使用新的统一日志框架。

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

4.4 验证与微调

启动应用并观察 gc.log。重点查看日志中的 Pause 相关行。

G1 日志示例(关注 Total 时间):

[GC pause (G1 Evacuation Pause) (young), 0.2301234 secs]

ZGC 日志示例(关注 PauseGC 的分离):

[info] gc(start), GC(1) Pause Init Mark 0.452ms
[info] gc(start), GC(1) Concurrent Mark 15.203ms

对比停顿时间。如果发现停顿确实稳定在 10ms 以内,但 CPU 使用率显著上升,这是正常现象。如果 CPU 无法承受,可以考虑适当增加堆大小来降低 GC 频率,或者退回 G1 并通过 -XX:MaxGCPauseMillis 放宽延迟要求以换取吞吐量。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文