还记得标记时候的重定位么?在写读屏障时候提到的 forwardingTable 就是个映射集,你可以理解为 key 就是对象转移前的地址,value 是对象转移后的地址。
不过这个映射集在标记阶段已经用了,也就是说标记的时候已经重定位完了,所以现在没用了。
但新一轮的垃圾回收需要还是要用到这个映射集的。
因此在这个阶段对那些转移分区的地址映射集做一个复位的操作。
回收无效分区回收那些物理内存已经被释放的无效的虚拟内存页面。
就是在内存紧张的时候会释放物理内存,如果同时释放虚拟空间的话也不能释放分区,因为分区需要在新一轮标记完成之后才能释放。
所以就会有无效的虚拟内存页面存在,在这个阶段回收。
选择待回收的分区这和 G1 一样,因为会有很多可以回收的分区,会筛选垃圾较多的分区,来作为这次回收的分区集合。
初始化待转移集合的转移表这一步就是初始化待回收的分区的 forwardingTable。
初始转移这个阶段其实就是从根集合出发,如果对象在转移的分区集合中,则在新的分区分配对象空间。
如果不在转移分区集合中,则将对象标记为 Remapped。
注意这个阶段是 STW,只转移根直接可达的对象。
并发转移这个阶段和并发标记阶段就很类似了,从上一步转移的对象开始遍历,做并发转移。
这一步很关键。
G1 的转移对象整体都需要 STW,而 ZGC 做到了并发转移,所以延迟会低很多。
至此十个步骤就完毕了,一次 GC 结束。
可以还能同学对染色指针的几个标记位有点懵,没事看了下文就懂了。
染色指针的标记位来分析下几个标记位,M0、M1、Remapped。
先来介绍个名词,地址视图:指的就是此时地址指针的标记位。
比如标记位现在是 M0,那么此时的视图就是 M0 视图。
在垃圾回收开始前视图是 Remapped 。
在进入标记标记时。
标记线程访问发现对象地址视图是 Remapped 这时候将指针标记为 M0,即将地址视图置为 M0,表示活跃对象。
如果扫描到对象地址视图是 M0 则说明这个对象是标记开始之后新分配的或者已经标记过的对象,所以无需处理。
应用线程 如果创建新对象,则将其地址视图置为 M0,如果访问的对象地址视图是 Remapped 则将其置为 M0,并且递归标记其引用的对象。
如果访问到的是 M0 ,则无需操作。
标记阶段结束后,ZGC 会使用一个对象活跃表来存储这些对象地址,此时活跃的对象地址视图是 M0。
并发转移阶段,地址视图被置为 Remapped 。
也就是说 GC 线程如果访问到对象,此时对象地址视图是 M0,并且存在或活跃表中,则将其转移,并将地址视图置为 Remapped 。
如果在活跃表中,但是地址视图已经是 Remapped 说明已经被转移了,不做处理。
应用线程此时创建新对象,地址视图会设为 Remapped 。
此时访问对象如果对象在活跃表中,且地址视图为 Remapped 说明转移过了,不做处理。
如果地址视图为 M0,则说明还未转移,则需要转移,并将其地址视图置为 Remapped 。
如果访问到的对象不在活跃表中,则不做处理。
那 M1 什么用?
M1 是在下一次 GC 时候用的,下一次的 GC 就用 M1来标记,不用 M0。
再下一次再换过来。
简单的说就是 M1 标识本次垃圾回收中活跃的对象,而 M0 是上一次回收被标记的对象,但是没有被转移,在本次回收中也没有被标记活跃的对象。
其实从上面的分析以及得知,如果没有被转移就会停留在 M0 这个地址视图。
而下一次 GC 如果还是用 M0 来标识那混淆了这两种对象。
所以搞了个 M1。
至此染色指针这几个标志位应该就很清晰了,我在用图来示意一下。
不清晰的同学建议再多看几遍标记位的变更,不复杂的。
最后简单的总结下,ZGC 就是通过多阶段的并发和几个短暂的 STW 阶段来达到低延迟的特性。
利用指针染色技术和读屏障实现并发转移对象,利用 STAB 保证并发阶段不会漏标对象。
这一波一下相信大家对于 ZGC 有了一定的了解。
我个人认为重点就掌握官网罗列的那几个要点就行,毕竟咱们也不是写 GC 的,作为了解即可。
到时候和学妹呀,或者在面试官前面呀都可以小吹一下。
如果想深入了解当然可以,可先看看《新一代垃圾回收器ZGC设计与实现》这本书,然后再源码走起。
ZGC 的不分代其实是它的缺点,因为分代比较难实现,不过以后应该会加上吧。
其实从现代垃圾收集器的演进可以看出就是往并发上面靠,目标就是减少停顿的时间。
不过并发需要注意内存分配的速率,因为并发导致一次垃圾回收总的时间变长了。
如果内存分配过快那就回收不过来了,因此都需要预留内存空间或者说要更大的内存空间来应对快速的分配速率。
可能大伙还惦记这标题吧?ZGC 的 Z 是什么意思?
其实没啥意思,就是个名字而已。