【JVM】低延迟垃圾收集器:Shenandoah收集器与ZGC收集器
正如标题所言,下文要介绍的两个收集器都是低延迟的垃圾收集器。首先要明确低延迟的概念,在垃圾回收阶段就是低STW时间。从传统的G1、CMS相比,它们在垃圾的初始标记、回收后的整理阶段都是STW的,尤其是整理阶段的STW时间很长。因此为了达到低延迟的目的,Shenandoah收集器与ZGC收集器不仅要进行并发的垃圾标记,还要并发地进行对象清理后的整理动作。
Shenandoah收集器
特点
Shenandoah收集器更像是继承G1的垃圾收集器,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致,甚至还直接共享了一部分实现代码。
那么Shenandoah收集器又做了哪些改进呢?
- 支持并发的整理算法
- 默认不使用分代收集,不会有专门的新生代Region或者老年代Region的存在
- 摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系
连接矩阵可以理解为一个二维布尔矩阵:矩阵的行和列都代表堆中的 Region,若矩阵中
matrix[1][3] = true
,则表示 Region 1 中存在引用指向 Region 3 中的对象;反之则无。
工作流程
初始标记(Initial Marking)
与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍是 STW 的。
并发标记(Concurrent Marking)
与G1一样,遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的
最终标记(Final Marking)
与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。会有一小段的停顿。
并发清理(Concurrent Cleanup)
清理没有存活对象的Region,这类Region被称为ImmediateGarbage Region。
并发回收(Concurrent Evacuation)
把回收集里面的存活对象先复制一份到其他未被使用的Region之中。由于这个阶段是并发执行的,要保证对象能在移动的同时也能够被读写访问。Shenandoah将会通过读屏障和被称为“Brooks Pointers”的转发指针来解决。下文将要详细介绍转发指针。
初始引用更新(Initial Update Reference)
建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务并发引用更新(Concurrent Update Reference)。会产生一个非常短暂的停顿。
并发引用更新(Concurrent Update Reference)
真正开始进行引用更新操作,这个阶段是与用户线程一起并发的。
最终引用更新(Final Update Reference)
修正存在于GC Roots中的引用。会产生停顿。
并发清理(Concurrent Cleanup)
经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象。这个阶段用来回收这些Region的内存空间。是并发的。
Brooks Pointer
在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。当对象复制到新的Region的时候,这个指针就指向新的Region中的对象,使对该对象的访问转移到新副本上。并且旧对象依然存在,只要没有被清理依然可以使用。
通过同时设置读、写屏障去拦截访问对象的操作,来保证并发时原对象与复制对象的访问一致性。
性能
停顿时间比其他几款收集器确实有了质的飞跃,但也并未实现最大停顿时间控制在十毫秒以内的目标,而吞吐量方面则出现了很明显的下降,其总运行时间是所有测试收集器中最长的。
ZGC收集器
特点
ZGC收集器是一款基于Region内存布局的,不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC的Region可以根据大小分为三种:
- 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
- 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
- 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。
工作流程
并发标记(Concurrent Mark)
与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍是 STW 的。然后处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。会有一小段的停顿。
并发预备重分配(Concurrent Prepare for Relocate)
根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。
并发重分配(Concurrent Relocate)
把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。通过染色指针直到对象是否在重分配集中。
如果用户此时访问了重分配集的对象,会被内存屏障阻拦,根据转发表访问到新对象,然后修改当前引用为新对象。
并发重映射(Concurrent Remap)
修正重分配集中旧对象的引用至新对象,合并到了下一次垃圾收集循环中的并发标记阶段里去完成。
染色指针
ZGC使用染色指针把标记信息记在引用对象的指针上,通过这些标志信息,虚拟机可以直接从指针中看到其引用对象的三色标记状态、是否进入了重分配集(即被移动过)、是否只能通过finalize()方法才能被访问到。
优势:
- 当Region中没有存活对象的时候,可以被立刻回收
- 大幅减少在垃圾收集过程中内存屏障的使用数量
- 可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能
性能
ZGC与Parallel Scavenge、G1三款收集器通过SPECjbb 2015[插图]的测试结果。在ZGC的“弱项”吞吐量方面,以低延迟为首要目标的ZGC已经达到了以高吞吐量为目标Parallel Scavenge的99%,直接超越了G1。如果将吞吐量测试设定为面向SLA(ServiceLevel Agreements)应用的“Critical Throughput”的话[插图],ZGC的表现甚至还反超了Parallel Scavenge收集器。