1. ZGC介绍
ZGC(The Z Garbage Collector)是 JDK 11 中推出的一款低延迟垃圾回收器,为实现以下几个目标而诞生的垃圾回收器,停顿时间不超过 10ms,停顿时间不会因堆变大而变长,支持 8MB~4TB 级别的堆(未来支持 16TB)
2. ZGC内存和原理
2.1 ZGC内存分布
ZGC 与传统的 CMS、G1 不同、它没有分代的概念,只有类似 G1 的 Region 概率;在划分内存的方式上,ZGC 与 G1 有着相似的地方,都舍弃了年轻代、老年代的划分方式,但是又与 G1 的形式不太一样,ZGC 是页为单位进行划分,一般分为三种页面:
- 小型 Region(Small Region):容量固定为 2MB,用于放置小于 256KB 的小对象。
- 中型 Region(Medium Region):容量固定为 32MB,用于放置大于 256KB 但是小于 4MB 的对象。
- 大型 Region(Large Region):容量不固定,可以动态变化,但必须为 2MB 的整数倍,用于放置 4MB 或以上的大对象。每个大型 Region 中会存放一个大对象,这也预示着虽然名字叫 “大型 Region”,但它的实际容量完全有可能小于中型 Region,最小容量可低至 4MB。大型 Region 在 ZGC 的实现中是不会被重分配的(重分配是 ZGC 的一种处理动作,用于复制对象的收集器阶段)因为复制大对象的代价非常高。
2.2 ZGC 原理
全并发的 ZGC垃圾回收流程: 与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记 - 复制算法,不过 ZGC 对该算法做了重大改进:ZGC 在标记、转移和重定位阶段几乎都是并发的,这是 ZGC 实现停顿时间小于 10ms 目标的最关键原因。
ZGC 垃圾回收周期如下图所示:
ZGC 只有三个 STW 阶段:初始标记,再标记,初始转移
初始标记和初始转移分别都只需要扫描所有 GC Roots,其处理时间和 GC Roots 的数量成正比,一般情况耗时非常短;再标记阶段 STW 时间很短,最多 1ms,超过 1ms 则再次进入并发标记阶段。即,ZGC 几乎所有暂停都只依赖于 GC Roots 集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与 ZGC 对比,G1 的转移阶段完全 STW 的,且停顿时间随存活对象的大小增加而增加。
3. ZGC技术特性
3.1 着色指针
着色指针(Colored Pointers)是 ZGC 的关键技术。ZGC 之前的垃圾回收器 JVM 将对象的 GC 信息记录在对象头 Mark Word 中,GC 进行标记的时候遍历 GC Roots 的对象然后对 Mark Word 的信息进行修改。而 ZGC 将 GC 信息记录在指针中,标记算法不再寻找 Mark Word 中的信息,只需要找到相应的指针信息即可。
在 64 位架构的计算机中(ZGC 只支持 64 位), 一个 Java 对象 64 位,其中低位的 42 位是对象地址,42-45 位用来做标记信息,四个状态分别是 Marked0、Marked1、Remapped、Finalizable,剩下的 18 位预留以后使用。
创建对象时,JVM 先在堆空间申请一个内存地址,同时利用 MMAP 函数将该内存地址分别映射到 Marked0、Marked1,Remapped 完成多视图映射。在同一时间这三个视图有且仅有一个生效,这是一种 “空间换时间” 的思想。
3.2 读屏障
读屏障主要是用来解决指针在并行转移的过程中出现的问题。ZGC 进行并行转移时,GC 线程与 Java 应用线程同时工作,当 Java 应用线程读取一个未完成转移的对象的时候就会出现指针无效的问题。为了解决这个问题 ZGC 使用了读屏障的技术,当出现上述情况的时候,Java 应用线程必须在读取对象之前先把对象转移。同时 ZGC 设置了触发条件,只有在应用线程从内存堆中加载对象引用的情况下才会触发读屏障。读屏障根据着色指针记录的 GC 信息判断对象是否被移动过,如果对象发生过移动就需要对指针的内存地址进行修复。读屏障的触发条件可参考以下代码:
Object object = obj.FieldA ; // 从堆中读取对象引用,需要加入读屏障
Object o = object ; // 不需要加入读屏障,因为不是从堆中读取引用
object.doSomething (); // 不需要加入读屏障,因为不是从堆中读取引用
int i = obj.FieldB; // 不需要加入读屏障,因为不是对象引用
ZGC 中读屏障的代码作用:
GC 线程和应用线程是并发执行的,所以存在应用线程去 A 对象内部的引用所指向的对象 B 的时候,这个对象 B 正在被 GC 线程移动或者其他操作,加上读屏障之后,应用线程会去探测对象 B 是否被 GC 线程操作,然后等待操作完成再读取对象,确保数据的准确性。具体的探测和操作步骤如下:
4 总结
ZGC 作为下一代垃圾回收器,性能非常优秀。ZGC 垃圾回收过程几乎全部是并发,实际 STW 停顿时间极短,不到 10ms。这得益于其采用的着色指针和读屏障技术。