Skip to content

ZGC 垃圾收集器

GC 停顿太长了?

想象一下:你正在和用户交互,突然界面卡住 500ms,等 GC 回收完才恢复。这在金融交易、游戏、实时系统里是不可接受的。

ZGC 的目标就是:停顿时间不超过 1ms,不管堆多大

为什么需要 ZGC

传统 GC 的问题

GC停顿时间问题
Serial GC很长单线程,适合小型堆
Parallel GC数百 ms高吞吐,但停顿长
CMS数十 ms已废弃,有并发失败风险
G1数十到数百 ms可预测,但仍会停顿

问题核心:Stop-The-World(STW)。GC 时必须暂停所有应用线程,堆越大停顿越长。

ZGC 的设计目标

停顿时间: < 1ms
停顿时间不随堆大小增长: ✅
停顿时间不随活跃数据大小增长: ✅
停顿时间不随 root 扫描数量增长: ✅

核心原理

着色指针(Colored Pointers)

ZGC 用 64 位指针的元数据位来标记对象状态:

┌─────────────────────────────────────────────────────────────────┐
│                      64 位对象指针                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  46 位(对象地址)│ 4 位(着色位)│ 14 位(预留)                 │
│                                                                 │
│  着色位用途:                                                    │
│  ┌────┬────┬────┬────┐                                         │
│  │ Marked0│ Marked1│ Remapped│ Finalizable │                   │
│  └────┴────┴────┴────┘                                         │
│                                                                 │
│  逻辑三色:白色(未访问)→ 灰色(扫描中)→ 黑色(已处理)          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

通过着色指针,GC 可以在不暂停应用的情况下追踪对象。

读屏障(Load Barrier)

当应用代码读取对象引用时,ZGC 会插入一个小检查:

c
Object* read_barrier(Object* obj) {
    if (obj.is_remapped()) {
        return obj;  // 已是最新,直接返回
    }
    return obj.forward();  // 可能被移动,返回新地址
}

这个检查非常快(~3ns),比 GC 停顿的代价小得多。

并发执行

┌─────────────────────────────────────────────────────────────────┐
│                      ZGC 并发阶段                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Pause Mark Start     - 初始化标记(STW,极短)              │
│       ↓                                                           │
│  2. Concurrent Mark      - 并发标记对象                           │
│       ↓                                                           │
│  3. Pause Mark End      - 标记完成(STW,极短)                │
│       ↓                                                           │
│  4. Concurrent Prepare  - 准备回收,决定收集集合                 │
│       ↓                                                           │
│  5. Concurrent Relocate - 并发移动对象                           │
│       ↓                                                           │
│  6. Pause Relocate Start - 重定位开始(STW,极短)             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

只有初始化和最终阶段会短暂停顿,且时间固定。

基本使用

启用 ZGC

bash
# JDK 11-14
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar

# JDK 15+(更稳定)
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar

# JDK 21+(分代 ZGC,性能更好)
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar

设置堆大小

bash
# ZGC 建议设置最大堆大小
java -XX:+UseZGC \
     -Xms8g -Xmx8g \    # 初始和最大堆
     -jar app.jar

# ZGC 会尽可能保持低延迟
# 如果堆使用率超过阈值,会触发 GC

调优参数

核心参数

bash
# 最大停顿时间目标(软目标)
java -XX:+UseZGC \
     -XX:MaxGCPauseMillis=1 \
     -jar app.jar

# GC 线程数(默认自动)
java -XX:+UseZGC \
     -XX:ConcGCThreads=4 \
     -jar app.jar

# 禁用类卸载(JDK 15+)
java -XX:+UseZGC \
     -XX:-ClassUnloading \
     -jar app.jar

大页内存

bash
# 使用大页内存,提升性能
# Linux 配置大页
# echo 20 > /proc/sys/vm/nr_hugepages
# 或使用 -XX:+UseLargePages

java -XX:+UseZGC \
     -XX:+UseLargePages \
     -Xms16g -Xmx16g \
     -jar app.jar

NUMA 支持

bash
# 启用 NUMA 感知(多插槽服务器)
java -XX:+UseZGC \
     -XX:+UseNUMA \
     -Xms64g -Xmx64g \
     -jar app.jar

完整配置示例

bash
java -XX:+UseZGC \
     -Xms16g -Xmx16g \
     -XX:MaxGCPauseMillis=1 \
     -XX:+UseLargePages \
     -XX:+UseNUMA \
     -XX:ConcGCThreads=8 \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

分代 ZGC(JDK 21+)

JDK 21 引入了分代 ZGC,进一步提升性能:

bash
# JDK 21+:默认就是分代 ZGC
java -XX:+UseZGC -Xms16g -Xmx16g -jar app.jar

# 分代 ZGC 的优势:
# - 年轻对象回收更频繁,停顿更短
# - 老年代对象回收频率降低
# - 内存占用更合理
┌─────────────────────────────────────────────────────────────────┐
│                   分代 ZGC 架构                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  年轻代(Young Generation)                                     │
│  ┌──────────┬──────────┬──────────┐                          │
│  │ Eden     │ S0        │ S1        │                          │
│  │ ┌─────┐ │ │ ┌─────┐ │ │          │                          │
│  │ │新对象│ │ │      │ │ │          │                          │
│  │ └─────┘ │ │ │      │ │ │          │                          │
│  │ └─────┘ │ │ └─────┘ │ │          │                          │
│  └──────────┴──────────┴──────────┘                          │
│       ↑                                                           │
│       │ 晋升                                                      │
│       ↓                                                           │
│  老年代(Old Generation)                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                                                         │    │
│  │    ZGC 并发回收                                         │    │
│  │                                                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

监控与诊断

GC 日志

bash
# 详细 GC 日志
java -XX:+UseZGC \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

# 实时查看
jstat -gcutil <pid> 1000

# GC 日志示例
#[2024-01-15T10:30:00.123+0800] GC(123) ZGC Pauses: 0.12ms (0.12ms)
#[2024-01-15T10:30:00.456+0800] GC(124) ZGC Pauses: 0.08ms (0.08ms)

JMX 监控

java
import java.lang.management.*;

public class ZgcMonitor {

    public static void main(String[] args) {
        GCM beans:
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();

        for (GarbageCollectorMXBean bean : gcBeans) {
            if (bean.getName().contains("ZGC")) {
                System.out.println("Collector: " + bean.getName());
                System.out.println("Collection count: " + bean.getCollectionCount());
                System.out.println("Collection time: " + bean.getCollectionTime() + "ms");
            }
        }
    }
}

诊断命令

bash
# 查看 GC 统计
jcmd <pid> GC.class_histogram

# 查看堆信息
jcmd <pid> GC.heap_info

# 查看 ZGC 特定统计
jcmd <pid> VM.native_memory summary

适用场景

ZGC 表现好的场景

bash
# 场景一:大内存应用
java -XX:+UseZGC -Xms64g -Xmx64g -jar app.jar
# 几十 GB 堆,停顿 < 1ms

# 场景二:低延迟要求
java -XX:+UseZGC -XX:MaxGCPauseMillis=1 -jar app.jar
# 金融交易、游戏服务器、实时系统

# 场景三:容器环境
java -XX:+UseZGC -Xms8g -Xmx8g -jar app.jar
# Docker/K8s 场景效果尤佳

ZGC 不适合的场景

bash
# 场景一:超小堆(< 100MB)
# G1 或 Serial GC 更合适,开销更小

# 场景二:追求极致吞吐量
# Parallel GC 吞吐量更高,但停顿长

# 场景三:极简环境(没有 ZGC 支持)
# 某些特殊环境可能不支持 ZGC

ZGC vs 其他 GC

特性ZGCG1Parallel
停顿时间< 1ms数十-数百 ms数百 ms
停顿与堆大小无关相关相关
吞吐量最高
内存开销
JDK 正式版159(默认)很久以前
分代支持21+原生原生

小结

ZGC 是 JDK 11+ 最重磅的 GC 改进:

bash
# 简单使用
java -XX:+UseZGC -Xms8g -Xmx8g -jar app.jar

# 生产配置
java -XX:+UseZGC \
     -Xms16g -Xmx16g \
     -XX:MaxGCPauseMillis=1 \
     -XX:+UseLargePages \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

什么时候选 ZGC:

  • 堆内存 > 4GB
  • 对延迟敏感(< 100ms 不可接受)
  • 容器化部署(Docker/K8s)
  • JDK 15+ 生产可用,JDK 21+ 推荐使用

基于 VitePress 构建