说说ZGC的工作原理?

3 个回答

  • 韩湘子
    为了击碎你伪装的表面,我只能用尽全身力气挥出那惊鸿的半月弧光。
    ## 1. ZGC 想解决什么问题? 设计目标一句话: > **在几十 GB~几 TB 堆上,把 GC 停顿时间压到毫秒级甚至亚毫秒级,并且还能做压缩(移动对象)。** ([OpenJDK Wiki][1]) 关键点有三个: 1. **几乎所有工作并发做**:标记、整理、移动对象都在应用线程运行时完成; 2. **停顿非常短**:只有少数几个 STW 同步点(起标记、结束标记、开始迁移),设计上控制在 <1ms; 3. **能并发压缩**:不像 G1 那样需要在 STW 里挪对象,ZGC 是边跑边挪。 做到这一点,靠两大核心技术: * **有色指针(Colored Pointers)** * **读屏障 / 加载屏障(Load Barriers)** ([AlibabaCloud][2]) ## 2. 内存布局:Region + 有色指针 ### 2.1 堆是 Region 化的 和 G1 一样,ZGC 把堆切成一块块 **Region**(也叫 ZPage),大小不一样:小对象用小页,大对象用大页,方便局部性和大对象管理。堆布局是 **多段区域 + NUMA 感知**,不是单一大连续块。([开发.Java][3]) ### 2.2 有色指针:在指针里塞元数据 ZGC 利用 64 位地址的高位没用完这件事,把一部分高位拿来存 GC 状态,形成所谓 **有色指针(colored pointer)**:([AlibabaCloud][2]) > 低位:对象真实地址 > 高位几 bit:颜色 / 状态(标记状态、是否已经重映射、是否在重定位区域等) 所以一个引用本身就带着“我现在在 GC 哪个阶段 / 什么状态”的信息。 读引用的时候,只要看一下颜色,就知道要不要做额外处理。 ## 3. Load Barrier:所有“读引用”都过一道闸 **ZGC 不像 G1/CMS 依赖写屏障,而是强依赖“读屏障”**:每次从堆里加载一个对象引用,都经过一段检查逻辑——这就是 **Load Barrier**。([ACM Digital Library][4]) 伪代码类似这样(概念级): ```java // 假设 obj.field 是一个 heap 引用 ref = obj.field; // 普通 load ref = loadBarrier(ref); // ZGC 加的屏障 use(ref); ``` `loadBarrier(ref)` 做几件事(按阶段不同略有变化,大意如下):([AlibabaCloud][2]) 1. **标记阶段**:如果 ref 指向的对象还没被标记,就把它标记成“活的”(等价于 tri-color 的灰→黑),并加入标记队列; 2. **重定位准备阶段**:如果 ref 指向的是“将要被迁移的 Region”,可能要触发一次“提前移动”/记录转发表; 3. **重映射阶段**:如果 ref 还是旧地址(from-space),就根据转发表/元数据把它修正为新地址(to-space 地址)。 **关键是:** * **正常情况(颜色正确)会走超级短的 fast-path**,性能接近普通指针读; * **只有少数“颜色不对”的引用才会走 slow-path,做标记/搬迁/修正。** ([开发.Java][3]) 这就是 ZGC 能在应用线程跑着的时候并发搬对象、并发标记,还保证所有读到的引用都是“最新安全版本”的原因。 ## 4. 一次 ZGC 周期的整体流程 (这里先说“单代 ZGC”的思路,分代版只是把这个流程套到 Young / Old 上) 一次完整 GC 周期可以大致拆成这些阶段(命名略随 JDK 版本有细微差异):([开发.Java][3]) 1. **Pause Mark Start(STW 短暂停)** * 停顿所有线程一小会儿; * 从 GC Roots(线程栈、全局静态、JNI Handle 等)标记初始可达对象,把它们“接入”并发标记队列; * 立刻恢复应用线程。 2. **Concurrent Mark(并发标记)** * GC 线程和应用线程一起跑; * 遍历对象图,给所有可达对象打上“活着”的标记(实质上是更新有色指针 / bitmap); * 期间所有对引用的读,都经过 Load Barrier: * 发现尚未标记的对象就补标,保证 “快照时可达对象都能被标记”。 3. **Pause Mark End(STW)** * 短暂停,做收尾: * 处理残余的并发队列; * 完成根集的最后一轮检查; * 统计每个 Region 的存活率,为后面选“搬谁”做准备。 4. **Concurrent Prepare for Relocate(并发准备迁移)** * 挑出一部分 **垃圾比例高的 Region** 组成 Relocation Set; * 分配相应的 to-space 区域; * 建一些元数据结构(转发表、位图等)。 5. **Pause Relocate Start(STW 短暂停)** * 短暂停,做一点点同步: * 把要迁移区域里的根引用处理一下; * 切换 GC 状态,让后续 Load Barrier 知道现在进入“重定位阶段”。 6. **Concurrent Relocate(并发迁移)** * GC 线程把 Relocation Set 里的活对象拷贝到新 Region(to-space); * 对象在被搬迁过程中,Load Barrier 可以做到: * 应用线程如果还拿着旧引用,读出来发现颜色“不对”,就去查转发表 / 元数据,把指针修正成新地址; * 必要时甚至会“现搬现修”。 7. **Concurrent Remap / Cleanup** * 一边继续通过 Load Barrier 修正旧引用; * 一边清理已经完全“无引用”的旧 Region,把它们放回空闲列表; * 周期结束,进入下一轮。 整个过程中只有几次非常短的 STW: * Mark Start * Mark End * Relocate Start 这些停顿和堆大小基本无关,更跟 **线程数、root 数量** 更相关,所以可以做到 TB 堆上仍是 <1ms 级别。([OpenJDK Wiki][1]) ## 5. 为什么 ZGC 能“并发压缩而不 Full GC 大停顿”? 传统压缩(比如 Serial/Parallel/G1 的压缩阶段)的问题: * 要移动对象; * 要更新所有引用指向的新地址; * 如果想一次性把所有指针都更新干净,只能停世界找所有引用、全堆扫一遍 → 巨大 STW。 ZGC 的核心 trick: 1. **不一次性更新所有引用** * 只要保证:“任何时候应用读到一个引用时都是对的”。 * 至于有多少“潜在的旧引用”还散在某些地方,没关系,让 Load Barrier 慢慢修。 2. **用有色指针 + Load Barrier 区分“新旧地址”** * 旧地址上的对象搬走后仍然存在一段时间(from-space),但它们的“颜色”说明这是旧的或待迁移的;([Reddit][5]) * Load Barrier 一看颜色不对,就知道这个引用要修正/这个对象要搬; * 修完再把颜色改对,就不会再触发 slow path。 等到某个 Region 里: * 所有对象要么已经搬完; * 所有仍然可能被访问的引用都已经被 Barrier 修成新地址; 这块 Region 就可以真正回收掉,完全在线完成,没有一次长时间 STW。 ## 6. 分代 ZGC(JDK 21+)在这套机制上加了什么? 从 JDK 21 开始,ZGC 有了 **Generational ZGC(分代 ZGC)**:([Inside.Java][6]) * 仍然是 **Region + 有色指针 + Load Barrier** 那套底层机制; * 但是引入 **Young / Old Generation** 的概念,把“朝生夕死”的对象和长寿对象分层管理,明显降低整体 GC 成本、提升吞吐; * JDK 24 起非分代模式已经移除,ZGC 默认就是分代模式。([Oracle 文档][7]) 分代 ZGC 的变化大致是: 1. Region 被标记为 `young` / `old`,新对象先进 young; 2. 多数周期只做 **Young Collection**,老年代少收; 3. 老年代不够干净时,触发 **Old / Mixed Collection**,但仍然是并发为主、短 STW; 4. 写屏障 + 卡表用来记录从 Old → Young 的引用,避免老年代每次都全量扫。 底层仍然是: * 标记:Load Barrier 参与; * 迁移:有色指针 + Load Barrier; * 清理:并发完成。 ## 7. 总结一页话 你可以把 ZGC 记成这几个关键词: 1. **目标**:极低停顿(<1ms 级),大堆(GB~TB),并发标记 + 并发压缩。([OpenJDK Wiki][1]) 2. **内存模型**:Region 化堆 + 有色指针(指针里塞 mark/remap 状态)。([AlibabaCloud][2]) 3. **屏障模型**:重度依赖 **Load Barrier**,所有“读引用”都经过检查,负责:标记、搬家、修指针。([ACM Digital Library][4]) 4. **GC 周期**:多个并发阶段 + 几个极短 STW(Mark Start / Mark End / Relocate Start),所有“重活”并发做。([开发.Java][3]) 5. **分代版(JDK21+)**:在这套机制上叠加 Young/Old 分代,降低整体 GC 工作量和开销,现在是 ZGC 的默认模式。([Oracle 文档][7]) 如果你愿意,下一步我可以帮你对比一下 **G1 vs ZGC**:从暂停时间模型、吞吐、内存占用、调优复杂度几个维度来对比,顺便给一套“什么时候值得从 G1 换 ZGC”的判断模板。 [1]: https://wiki.openjdk.org/display/zgc/Main?utm_source=chatgpt.com "Main - ZGC" [2]: https://www.alibabacloud.com/blog/alibaba-dragonwell-zgc-part-2-the-principles-and-tuning-of-zgc-%7C-a-new-garbage-collector_598851?utm_source=chatgpt.com "The Principles and Tuning of ZGC | A New Garbage Collector" [3]: https://dev.java/learn/jvm/tool/garbage-collection/zgc-deepdive/?utm_source=chatgpt.com "Deep-dive of ZGC's Architecture" [4]: https://dl.acm.org/doi/full/10.1145/3538532?utm_source=chatgpt.com "Deep Dive into ZGC: A Modern Garbage Collector in ..." [5]: https://www.reddit.com/r/java/comments/132nz6y/z_garbage_collector_the_next_generation/?utm_source=chatgpt.com "Z Garbage Collector: The Next Generation : r/java" [6]: https://inside.java/2023/11/28/gen-zgc-explainer/?utm_source=chatgpt.com "Introducing Generational ZGC" [7]: https://docs.oracle.com/en/java/javase/25/migrate/significant-changes-jdk-24.html?utm_source=chatgpt.com "Significant Changes in JDK 24 Release"
    发布于 2025-05-25 16:33 •IP属地 吉林
    举报
    评论
    收藏

  • 洛璃
    须知少日擎云志,曾许人间第一流。
    ZGC(Z Garbage Collector)是JDK 11后引入的低延迟垃圾回收器,主打特点是**极低的停顿时间(通常在10毫秒以内)**,适用于对延迟敏感的高性能应用。 ### 一、ZGC的核心工作原理 ZGC主要分为以下几个阶段: 1. **并发标记(Concurrent Mark)** - ZGC采用一种并发标记算法,在标记活跃对象时与Java应用程序线程同时运行。 - 利用“染色指针”(Colored Pointer)技术,直接在指针上标记对象状态,极大地降低了读写屏障的成本。 2. **并发准备重定位(Concurrent Prepare for Relocation)** - 确定哪些存活对象需要移动,并准备重定位集合,整个过程都是并发的。 - 此阶段不停止应用线程,几乎不引入额外延迟。 3. **并发重定位(Concurrent Relocation)** - 并发地将存活对象从旧的位置复制到新位置。 - 应用线程通过读写屏障无缝感知对象位置的变化,而不会明显暂停。 ### 二、ZGC停顿极低的关键原因(与其他GC相比) #### 1. **染色指针技术(Colored Pointer)** - ZGC使用指针的部分位数(当前实现为64位系统中高位的未使用位)存储额外元数据(颜色),标识对象状态(已标记、未标记、待移动等)。 - 无需访问对象头即可判断状态,极大降低了并发标记和重定位过程中的开销。 #### 2. **全并发(真正的Concurrent)回收** - ZGC的**标记、重定位等核心回收步骤**都在与应用线程并行运行,不再依赖于长时间的“Stop-The-World”(STW)暂停。 - STW仅用于少量的GC根(如线程栈、寄存器)扫描,时间极短。 #### 3. **读屏障(Load Barrier)优化** - ZGC使用高效的读屏障,每次访问对象时都会检查对象是否已移动并更新引用,从而确保对象始终可用。 - 相较于其他GC算法(如G1的写屏障、CMS的读写屏障),ZGC屏障的开销更低、优化更好。 #### 4. **分区管理与动态重定位** - ZGC的堆内存被分成许多小的分区(Region),更灵活地回收空间。 - 并发动态重定位时,ZGC只重定位部分对象,不必一次性搬移所有对象,这种渐进式回收进一步降低了停顿。 ### 三、ZGC与其他GC的停顿对比 | 垃圾回收器 | 工作模式 | 典型停顿时长 | | --------- | --------- | ----------- | | CMS | 并发标记+短暂STW回收 | 数百毫秒 | | G1 | 并发标记+渐进式STW回收 | 数百毫秒 | | **ZGC** | 全阶段并发,极短STW | **<10毫秒** | **结论:** ZGC停顿远低于其他GC,关键在于其几乎完全并发的工作方式、高效的染色指针与读屏障技术,以及极少量的STW根扫描
    发布于 2025-03-13 09:55 •IP属地 黑龙江
    举报
    评论
    收藏

  • 王平
    眼睛可以近视,目光不能短浅。
    与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。 ZGC垃圾回收周期如下图所示: ![image.png](https://images.codewd.com/b1c28373b16e077592b4e76352018d82.jpg "image.png") ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。 ### ZGC关键技术 ZGC通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,实现了并发转移。大致原理描述如下:并发转移中“并发”意味着GC线程在转移对象的过程中,应用线程也在不停地访问对象。假设对象发生转移,但对象地址未及时更新,那么应用线程可能访问到旧地址,从而造成错误。而在ZGC中,应用线程访问对象将触发“读屏障”,如果发现对象被移动了,那么“读屏障”会把读出来的指针更新到对象的新地址上,这样应用线程始终访问的都是对象的新地址。那么,JVM是如何判断对象被移动过呢?就是利用对象引用的地址,即着色指针。下面介绍着色指针和读屏障技术细节。 [美团zgc文章](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html)
    编辑于 2025-01-21 20:00 •IP属地 美国
    举报
    评论
    收藏