经典垃圾收集器(三)

低延迟垃圾收集器

衡量垃圾收集器的三项重要指标:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency)。三者构成了一个“不可能三角”

Shenandoah收集器

Shenandoah相比于G1有什么改进?

虽然Shenandoah也是基于Region的堆内存布局,同样有着用于存放大对象的Humongous Region,默认策略也是优先处理回收价值最大的Region,但是在堆内存管理上,它与G1至少有着三个明显的不同之处,最重要的是支持并发的整理算法,G1的回收阶段是可以多线程并行的,但却不能与用户线程并发。其次,Shenandoah目前默认不支持分代收集,即不会有专门的新生代Region或者老生代Region的存在,没有实现分代。最后,Shenandoah掘弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题的发生概率。

Shenandoah收集器工作的九个阶段:

  • 初始标记:与G1一样,首先标记与GCRoots直接关联的对象,这个阶段仍然是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量有关;
  • 并发标记:与G1一样,遍历对象图,标记出全部可达对象,这个阶段是与用户线程一起并发的,时间长短取决于堆中存活对象数量以及对象图结构的复杂程度;
  • 最终标记:与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记也会有一小段短暂停顿;
  • 并发清理:这个阶段用于清理那些整个区域一个存活对象都没有的Region,这类Region被称为Immediate Garbage Region;
  • 并发回收:并发回收阶段是Shenandoah与之前HotSpot收集器的核心差异;在这个阶段,Shenandoah要把回收集里面存活的对象先复制一份到其他未使用的Region之中。复制对象的线程与用户线程在这时是并发的,要实现这个其困难点在于:移动对象同时,用户线程仍然可能不停对被移动对象进行读写访问,移动对象是一次性行为,但是移动之后整个内存中所有指向该对象的引用都还是旧对象的地址,这很难一瞬间全部变过来。对于并发回收遇到的这些困难,Shenandoah将通过读屏障和被称为“Brooks Pointers"的转发指针来解决,并发回收阶段运行时间长短取决于回收集的大小;
  • 初始引用更新:并发回收阶段复制对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,初始引用更新时间很短,会产生一个非常短暂的停顿;
  • 并发引用更新:真正开始进行引用更新操作,这个阶段与用户线程一起并发,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不同,它不需要沿着对象图来搜索,只需要按照内存物理地址顺序,线性地搜索出引用类型,把旧值改为新值即可;
  • 最终引用更新:解决了堆中的引用更新后,还要修正存在于GC Roots中的引用,这个阶段是Shenandoah的最后一次停顿,停顿时间只于GC Roots的数量有关;
  • 并发清理:经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Region了,最后再调用一次并发清理线程来回收这些Region的内存空间,供以后、新对象分配使用。

ZGC收集器

ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

与之前基于Region布局的垃圾收集器不同的是,ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小。在X64硬件平台下,ZGC的Region可以具有大、中、小三类容量:

  • 小型Region:容量固定为2MB,用于放置小于256KB的小对象。
  • 中性Region:容量固定为32MB,用于放置大小等于256KB但小于4MB的对象。
  • 大型Region:容量不固定,可以动态变化,但必须是2MB的整数倍,用于放置4MB或者以上的大对象。

ZGC的核心问题——并发整理算法的实现

Shenandoah使用转发指针和读屏障来实现并发整理,ZGC也同样采用了读屏障,但是ZGC收集器有一个标志性的设计是它采用的染色指针技术。

 ZGC的染色指针是直接的、纯粹的,它直接把标记信息记在引用对象的指针上。染色指针是一种直接将少量额外的信息存储在指针上的技术。

优势:

  • 染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理;
  • 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障的目的通常是为了记录对象引用变动的情况,如果将这些信息直接维护在指针中,显然就可以省去一些专门记录的操作;
  • 染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关数据,以便日后进一步提高性能。

ZGC运作过程:

  • 并发标记:与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的 阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记(尽管ZGC中的名字不叫这些)的短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志位。
  • 并发预备重分配:这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。重分配集与G1收集器的回收集(Collection Set)还是有区别的,ZGC划分Region的目的并非为了像G1那样做收益优先的增量回收。相反,ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。因此,ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面 的Region会被释放,而并不能说回收行为就只是针对这个集合里面的Region进行,因为标记过程是针对全堆的。此外,在JDK 12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的。
  • 并发重分配:重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。
  • 并发重映射:重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,这一点从目标角度看是与 Shenandoah 并发引用更新阶段一样的,但是 ZGC 的并发重映射并不是一个必须要“迫切”去完成的任务,因为前面说过,即使是旧引用,它也是可以自愈的,最多只是第一次使用时多一次转发和修正操作。重映射清理这些旧引用的主要目的是为了不变慢(还有清理结束后可以释放转发表这样的附带收益),所以说这并不是很“迫切”。因此,ZGC 很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象的开销。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值