ZGC 简介
**ZGC(Z Garbage Collector)**是 Java HotSpot 虚拟机自 JDK 11 引入的一款可扩展、低延迟的垃圾回收器,专为大堆内存和对延迟极为敏感的应用场景设计。
主要特点
- 超低延迟:GC 停顿时间通常不超过 1~2 毫秒,与堆大小几乎无关(即使 TB 级堆)。
- 并发回收:大部分垃圾回收工作与应用线程并发进行,极大减少 Stop-The-World(STW)时间。
- 可扩展性强:支持超大堆(数 TB 级别),适合大数据、在线服务等场景。
- Regionless 设计:不像 G1 那样分 Region,ZGC 采用按需分配的内存块(Chunk),简化内存管理。
- 着色指针(Colored Pointers):利用对象引用的高位存储元数据,实现高效的并发标记和转移。
工作原理
- 并发标记:与应用线程并发,标记所有可达对象。
- 并发重定位:对象在后台被迁移到新内存,引用通过着色指针和读屏障自动修正。
- 并发清理:回收不可达对象占用的内存。
- 极短暂停:只有少量阶段(如初始标记、重新标记)会有极短的 STW 停顿。
启用方式
- JDK 11+:
-XX:+UseZGC - JDK 15+ 支持 Windows/macOS
- 常用参数:
-Xmx、-Xms设置堆大小-XX:ConcGCThreads并发 GC 线程数-XX:MaxGCPauseMillis期望最大停顿时间
适用场景
- 需要极低 GC 停顿的在线服务
- 大内存、大对象、大吞吐量应用
- 金融、电商、广告、实时分析等领域
ZGC 的垃圾回收过程
ZGC 的回收过程高度并发,主要分为以下几个阶段:
初始标记(Initial Mark)
- 标记从 GC Roots 直接可达的对象。
- 该阶段会有一次极短的 Stop-The-World(STW)暂停。
并发标记(Concurrent Mark)
- 与应用线程并发执行,遍历对象图,标记所有可达对象。
- 利用着色指针和读屏障,保证标记期间对象引用变更的正确性。
重新标记(Relocate/Remark)
- 修正并发标记期间发生变动的对象引用。
- 该阶段也会有一次极短的 STW 暂停。
并发重定位(Concurrent Relocate/Move)
- 并发地将存活对象迁移到新的内存位置。
- 通过着色指针和读屏障,确保引用始终指向最新位置。
并发清理(Concurrent Cleanup)
- 回收不可达对象占用的内存块(Chunk),释放空间。
过程特点
- 绝大部分工作与应用线程并发进行,只有初始标记和重新标记阶段会有极短的 STW。
- 对象移动和引用修正通过硬件支持的着色指针和读屏障实现,极大降低了停顿时间。
- 整个回收过程对应用影响极小,适合对延迟极为敏感的场景。
ZGC 的着色指针与读写屏障
着色指针(Colored Pointers)
ZGC 利用 64 位系统对象引用的高位(未被实际寻址使用的位)来存储元数据,这些高位被称为“着色位”。通过这些着色位,ZGC 可以为每个对象引用打上不同的“颜色”,用来表示对象在 GC 各阶段的状态(如是否已被标记、是否已被转移等)。
- 作用:
- 跟踪对象的生命周期和迁移状态。
- 支持并发标记和并发重定位时的高效引用修正。
- 优势:
- 不需要为每个对象单独维护额外的元数据,节省内存和提升效率。
- 允许对象在被移动时,引用可以自动感知并指向新地址。
读写屏障(Read/Write Barrier)
ZGC 在对象访问时引入了读屏障和写屏障,用于配合着色指针实现并发标记和并发重定位:
读屏障(Read Barrier)
- 每次读取对象引用时,都会检查引用的着色位。
- 如果对象已被迁移,读屏障会自动将引用修正为新地址,保证应用始终访问到最新的对象位置。
- 读屏障还用于并发标记,确保对象状态正确。
写屏障(Write Barrier)
- 在对象引用发生变更时,写屏障会记录引用变动,协助 GC 跟踪对象间的引用关系变化。
- 保证并发标记和重定位期间的引用不会丢失。
总结
- 着色指针和读写屏障是 ZGC 实现极低延迟和高并发垃圾回收的核心机制。
- 它们让对象可以在 GC 过程中被安全地移动和标记,且对应用线程的影响极小。
通过这两项技术,ZGC 能在 TB 级堆下实现毫秒级停顿,极大提升了 Java 应用的可扩展性和实时性。
ZGC 是目前 Java 平台上延迟最低、可扩展性最强的垃圾回收器之一,适合对响应时间要求极高的业务场景。
ZGC 中存活对象的迁移
在 ZGC 中,是否迁移存活对象主要由以下因素决定:
内存碎片整理
当堆内出现较多碎片时,ZGC 会选择迁移存活对象,将它们移动到新的连续内存块(Chunk),以整理和回收碎片空间,提高大对象分配的成功率。内存回收策略
ZGC 会根据当前堆的使用情况、空闲空间分布和回收效率,动态决定哪些 Chunk 需要被清理和整理。被选中的 Chunk 内的存活对象会被迁移到其他 Chunk。并发重定位阶段
在并发重定位(Concurrent Relocate/Move)阶段,ZGC 会将需要整理的 Chunk 内的所有存活对象迁移到新位置,并更新所有相关引用(通过着色指针和读屏障实现)。分配需求
当需要为新对象分配大块连续空间时,如果当前没有足够的连续空闲 Chunk,ZGC 也会主动迁移存活对象,释放出大块空间。
总结
- 存活对象是否迁移,取决于内存碎片状况、堆空间利用率和分配需求。
- 迁移过程完全并发进行,对应用线程影响极小。
- 迁移后,所有引用会自动指向新位置,保证对象访问的正确性和一致性。
ZGC 中 Chunk 的管理方式
ZGC 采用“Chunk”作为内存分配和管理的基本单位,整个堆空间被动态划分为多个大小不等的 Chunk。Chunk 的管理方式如下:
1. Chunk 类型
- 小块(Small Chunk):用于分配小对象。
- 中块(Medium Chunk):用于分配中等大小对象。
- 大块(Large Chunk):用于分配大对象或特殊用途。
2. 动态分配与回收
- ZGC 会根据对象分配需求动态从操作系统申请新的 Chunk,也会在对象回收后将空闲 Chunk 归还或复用。
- Chunk 的分配和回收完全并发进行,不影响应用线程。
3. 空闲 Chunk管理
- ZGC 维护空闲 Chunk 的列表或池,分配新对象时优先复用空闲 Chunk,减少内存碎片和系统调用。
- 当空闲 Chunk 累积过多或堆压力较大时,ZGC 会主动整理和回收碎片空间。
4. Chunk 内部结构
- 每个 Chunk 内部有自己的元数据区,用于记录对象分配、存活、迁移等信息。
- Chunk 内部对象的分配、标记、迁移等操作均可并发完成。
5. 并发整理与碎片回收
- ZGC 在并发重定位阶段,会将存活对象从碎片较多的 Chunk 迁移到新的 Chunk,释放出连续空间并回收碎片。
- 迁移后,空闲的 Chunk 会被加入空闲池,等待后续分配或归还操作系统。
总结:
ZGC 通过 Chunk 的动态分配、并发管理和主动整理,有效提升了大堆场景下的内存利用率和分配效率,极大降低了碎片化风险
ZGC 与 G1 的优缺点对比
ZGC 优势
- 超低延迟:ZGC 的 GC 停顿时间通常在 1~2 毫秒,几乎与堆大小无关,非常适合对延迟极为敏感的应用。
- 极强可扩展性:支持数 TB 级堆,适合大数据、大内存场景。
- 高度并发:绝大部分回收工作与应用线程并发进行,对吞吐和响应影响极小。
- 自动整理碎片:通过并发重定位,有效缓解和整理内存碎片。
- Regionless 设计:内存管理更灵活,无需手动分区。
ZGC 劣势
- 成熟度和兼容性:ZGC 是较新的 GC,部分老版本 JDK、平台或第三方工具支持有限。
- 吞吐量略低:极端低延迟设计下,极高吞吐场景下可能不如 G1。
- 诊断工具较少:生态和调优经验不如 G1 丰富。
- 不支持 32 位 JVM:仅支持 64 位系统。
G1 优势
- 成熟稳定:G1 已成为 JDK 默认 GC,生产环境应用广泛,生态完善。
- 可预测停顿:支持
-XX:MaxGCPauseMillis,可根据业务需求调整停顿目标。 - 分代管理:新生代、老年代分区管理,适合大多数通用场景。
- 分区(Region)机制:便于内存管理和回收策略优化。
G1 劣势
- 停顿时间随堆增大而增加:虽然可控,但大堆下停顿时间仍明显高于 ZGC。
- 碎片整理能力有限:老年代碎片多时,可能导致 Full GC,影响延迟。
- 并发能力有限:部分阶段仍需较长 STW,极端低延迟场景下不如 ZGC。
总结
- ZGC 适合对延迟极为敏感、超大堆、实时性要求高的场景。
- G1 适合大多数对延迟有要求但不极端、需要成熟生态和调优经验的通用场景。