JVM垃圾收集器四:ZGC与颜色指针详解
# JVM垃圾收集器四:ZGC与颜色指针详解
# 一、简介
ZGC是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记-整理算法的, 以低延迟为首要目标的垃圾收集器。
ZGC是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于是Azul System公司开发的C4(Concurrent Continuously Compacting Collector) 收集器。
ZGC的Region可以分为三种:小型Region、 中型Region、 大型Region:
小型Region(Small Region) : 容量固定为2MB, 用于放置小于256KB的对象。
中型Region(Medium Region) : 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
大型Region(Large Region) : 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB及以上的大对象。 每个大型Region中只会存放一个大对象,
这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region, 最小容量可低至4MB。
大型Region是不会被重分配的(重分配是ZGC的一种处理动作, 用于复制对象的收集器阶段, 稍后会介绍到), 因为复制一个大对象的代价非常高昂。
# 二、垃圾回收过程
ZGC的收集过程主要分为四个阶段:
# 并发标记(Concurrent Mark)
和G1相同的是,并发标记是遍历对象图做可达性分析的阶段,它的初始标记(Mark Start)和最终标记(Mark End)也会出现短暂的停顿。
和G1不同的是, ZGC的标记是在指针上而不是在对象头上进行的, 并发标记阶段会更新颜色指针(见下面详解)中的Marked 0、 Marked 1标志位。
# 并发预备重分配(Concurrent Prepare for Relocate)
这个阶段需要根据特定的查询条件统计出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。
# 并发重分配(Concurrent Relocate)
这个阶段需要把重分配集中的存活对象复制到新的Region中,并且为重分配集中的每个Region维护了一个转发表(Forward Table),记录从旧对象到新对象的转向关系。
ZGC仅从引用上就能明确得知一个对象是否处于重分配集之中,如果此时用户线程并发访问了位于重分配集中的对象,那么这次访问将会被读屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。
ZGC的颜色指针因为“自愈”(Self-Healing)能力,所以只有第一次访问旧对象会变慢, 一旦重分配集中某个Region的存活对象都复制完毕后, 这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉, 因为可能还有访问在使用这个转发表。
# 并发重映射(Concurrent Remap)
重映射是为了修正整个堆中指向重分配集中旧对象的所有引用,但是ZGC中的对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。
ZGC很巧妙地把这一阶段要做的工作,合并到了下一次的并发标记阶段,反正它们都是要遍历所有对象的,这样也就减少了一次遍历对象图的开销。一旦所有指针都被修正之后, 那么Region中的转发表就可以释放掉了。
# 三、颜色指针
颜色指针(Colored Pointers),是ZGC的核心设计之一。以前的垃圾回收器将GC信息保存在对象头中,而ZGC将GC信息保存在指针中。
每个对象有一个64位指针,这64位被分为:
- 18位:预留给以后使用;
- 1位:Finalizable标识,与并发引用处理有关,表示这个对象只能通过finalizer才能访问;
- 1位:Remapped标识,设置此位的值后,表示对象已重映射,不再指向
relocation set
(relocation set表示需要GC的Region集合); - 1位:Marked1标识;
- 1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;
- 42位:对象的地址(所以它可以支持2^42=4T内存):
为什么有2个mark标记?
每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。
GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。
GC周期2:使用mark1, 则期待的mark标记10,所有引用都能被重新标记。
通过对配置ZGC后对象指针分析我们可知,对象指针必须是64位,那么ZGC就无法支持32位操作系统,同样的也就无法支持压缩指针了(CompressedOops,压缩指针也是32位)。
颜色指针的三大优势:
- 一个Region中的所有存活对象都被移走后,这个Region就可以被立即释放掉,因为它还有转发表。理论上,只要还有一个Region空闲,ZGC就能完成垃圾收集工作。
- 颜色指针有指针的“自愈”(Self-Healing)能力,这样子就减少了写屏障(例如三色标记中的增量更新或原始快照),只需要一个读屏障就可以解决问题,减少了内存屏障的使用数量。
- 颜色指针有着极大的扩展性,因为还有18位未使用,更有利于后续功能的扩展。
参考文章:https://wiki.openjdk.java.net/display/zgc/Main
http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf