染色指针 #
在 64 位系统下,当前 Linux 系统上的地址指针只用到了 48 位,寻址范围也就是 256T。但实际上,当前的应用根本就用不到 256T 内存,也没有哪台服务器机器上面可以一下插这么多内存条。所以, ZGC 就借用了地址的第 42 ~ 45
位作为标记位,第 0 ~ 41 位共 4T 的地址空间留做堆使用。我们结合 JVM 的源码来看看 ZGC 中对地址具体是怎么标注的。
通过上图我们可以看出,第 46 和 47 位是预留的,也就是说标记位可以继续向左移两位,那么可以支持的堆空间就可以扩展到 16T。当前很多资料说 ZGC 只支持 4T 内存,实际上现在最新版本已经支持到了 16T。
第 42-45 这 4 位是标记位,它将地址划分为 Marked0、Marked1、Remapped、 Finalizable 四个地址视图(由于 Finalizable 与弱引用的实现有关系,我们这里只讨论前三个)。
地址视图应该怎么理解呢?其实很简单,对一个对象来说,如果它地址的第 42 位是 1,那么它就被认为是处于 Marked0 视图。依次类推,如果第 43 位是 1,这个对象就处于 Marked1 视图;如果第 44 位是 1,该对象就处于 Remapped 视图。
有了地址视图之后,我们就可以在一个对象转移之后,修改它的地址视图了,同时还可以维护一张映射表(下称 forwarding table)。在这个映射表中,key 是旧地址,value 是新地址。当对象再次被访问时,通过插入的 read barrier 来判断对象是否被搬移过。如果 forwarding table 中有这个对象,说明当前访问的对象已经转移,read barrier 这时就会将对这个对象的引用直接更改为新地址。
搬移一个对象以及访问它的引用所需要的步骤,如下图所示:
上图中,当 foo 对象发生转移之后,对象 a 再访问 foo 时就会触发 read
barrier。read barrier 会查找 forwarding table 来确定对象是否发生了转移,确定 foo 被转移到新地址 foo(new)之后,直接将这一次对 foo 的访问更改为 foo(new)。由于整个过程是依托于 read barrier 自动完成的,这个过程也叫“自愈”。