Shenandoah 2.0 消除转发指针

Shenandoah 2.0 elimination of forwarding pointer

Shenandoah 1.0 中,每个 Object 都有一个额外的 header - 称为 forwarding pointer。为什么需要这样做,导致它在 Shenandoah 2.0 中被淘汰的原因是什么?

首先,每个java Object有两个header:klassmark。它们永远存在于每个实例中(例如,它们可以稍微改变 JVM 处理 their flags internally 与最近的 JVM 的方式)并且出于各种原因被使用(将仅详细介绍其中一个答案)。

forwarding pointer 字面意思是 the second part of this answerread barrierShenandoah 1.0 中的 write barrier 都需要 forwarding pointer(尽管 read 可以跳过某些字段类型的障碍 -就不细说了)。用非常简单的话来说,它大大简化了 并发复制 。如该答案所述,它允许自动将 forwarding pointer 切换到 Object 的新副本,然后 同时 更新所有引用以指向新的 Object.

Shenandoah 2.0 中的情况发生了一些变化,其中“to-space 不变量”就位:这意味着所有的写入和读取都是通过 to-space 完成的。这意味着一个有趣的事情:一旦建立了 to-space 副本,就永远不会使用 from-copy。想象一下这样的情况:

    refA            refB
      |               |
fwdPointer1 ---- fwdPointer2        
                      |
  ---------       ---------  
  | i = 0 |       | i = 0 | 
  | j = 0 |       | j = 0 | 
  ---------       ---------

Shenandoah 1.0 中,有些情况下 通过 refA 读取 可以绕过障碍(根本不使用它)和 仍然 阅读通过from-copy。例如,final 字段允许这样做(通过特殊标志)。这意味着即使 to-space 副本已经存在并且已经有对它的引用,仍然会有 reads(通过 refA)会转到 from-space复制。 Shenandoah 2.0 这是被禁止的。

这些信息的使用方式非常有趣。 Java 中的每个 object 都对齐到 64 位 - 这意味着最后 3 位 总是 零。因此,他们删除了 forwarding pointer 并表示:如果 mark 字的最后两位是 11 (这是允许的,因为没有其他人以这种方式使用它)-> 这是forwarding pointer,否则 to-space 副本还存在,这是一个普通的 header。你可以 see it in action right here and you can trace the masking here and here.

以前是这样的:

| -------------------|
| forwarding Pointer |
| -------------------|

| -------------------|
|        mark        |
| -------------------|

| -------------------|
|        class       |
| -------------------|

并转变为:

| -------------------|
| mark or forwarding |     // depending on the last two bits
| -------------------|

| -------------------|
|        class       |
| -------------------|

所以这是一种可能的情况(为简单起见,我将跳过 class header):

  refA, refB            
       |               
      mark   (last two bits are 00)   
       |              
    ---------   
    | i = 0 |      
    | j = 0 |      
    ---------  

GC 开始。 refA/refB 引用的 object 是活的,因此必须撤离(据说在“collection 集合中”) .首先创建一个副本,并以原子方式 mark 引用该副本(最后两位标记为 11 现在使其成为 forwardee 而不是 mark word ):

  refA, refB            
       |               
     mark (11) ------  mark (00)   
                           |
    ---------          ---------
    | i = 0 |          | i = 0 |
    | j = 0 |          | j = 0 |
    ---------          ---------

现在 mark word 之一有一个位模式(以 11 结尾)表明它是 forwardee 而不是标记词再也没有了。

       refA              refB            
         |                 |               
     mark (11) ------  mark (00)   
                           |
    ---------          ---------
    | i = 0 |          | i = 0 |
    | j = 0 |          | j = 0 |
    ---------          ---------

refB 可以并发移动,所以 refA 最终没有对 from-space object 的引用,它是垃圾。如果需要,这就是 mark word 作为 forwarding pointer 的方式。