0

Shenandoah对于看过发展的人来说,一个主要的批评是它对每一次写入和读取都使用GC barriers无论是参考还是原始,这并不是什么大秘密。

Shenandoah 2.0声称这不再是问题,并且可以通过所谓的负载参考障碍来解决。这到底是怎么回事?

4

1 回答 1

3

我假设读者知道什么是障碍以及为什么需要它。对于一个非常简短的介绍,这里是我关于该主题的另一个答案

为了正确理解这一点,我们需要首先看看最初的问题到底在哪里。让我们举一个相当简单的例子:

static class User {
     private int zip;
     private int age;
}

static class Holder {
    
     private User user;
     // other fields we don't care about
      
}

现在让我们想象一个这样的理论方法:

public void access(Holder holder){
     User user = holder.user;
     for(;;){ // some loop here
         int zip = user.zip;
         System.out.println(zip);

         user.age = // some value taken from the loop for example
     }
}

这个想法不是展示一个正确的例子,而是一个例子:

  • 一个( user.zip;)

  • ( user.age = ...) _

现在因为Shenandoah 1.0需要在任何地方引入障碍,这段代码看起来:

public void access(Holder holder){
  User user = RB(holder).user;  
  for(;;){ // some loop here
      int zip = RB(user).zip;
      System.out.println(zip);

      WB(user).age = // some value taken from the loop for example
  }
}   

注意RB(holder).user(RB代表read barrier) 和WB(user).age(WB代表write barrier)。现在想象一下,循环是hot——你将为如此多的障碍付出代价。即使在循环执行期间没有 GC 活动,屏障仍然存在,并且必须有代码有条件地检查是否需要执行屏障。

长话短说:无论如何,这些障碍都不是免费的。

这些屏障是保持堆一致性所必需的,因为在疏散阶段内存中有一个 Object 的两个副本,您需要始终保持一致的读取和写入。这里一致意味着读取可能发生在“to-space”或“from-space”(称为“弱到空间不变量”),而写入可能仅发生.Shenandoah 1.0to-space


Shenandoah 2.0表示它将确保所谓的“空间不变量”(与之前的变量相反)。基本上 - 它说所有的写入和读取都将发生在/进入“to-space”。在疏散期间,对象有两份副本:一份在旧区域(称为“从空间”),另一份在新区域(称为“到空间”)。

它通过一个相当简单但绝妙的想法实现了这个“到空间”不变量。它不是在发生的地方使用障碍writes,而是确保最初加载的对象肯定是从“到空间”加载的。这是通过load-reference-barriers完成的。通过重构前面的示例来理解这一点要简单得多:

  public void access(Holder holder){
      User user = LRB(holder).user;  
      for(;;){ // some loop here
          int zip = user.zip;
          System.out.println(zip);

          user.age = // some value taken from the loop for example
      }
  }

我们已经引入了LRB屏障并移除了另外两个。因此,加载引用障碍发生在加载对象时,它们在定义站点调用 this ,而不是在读取或存储到它时,他们在其使用站点调用 this 。您可以将其视为将这些障碍插入到使用aloadgetField(用于引用)的位置。

于 2020-09-20T03:04:16.193 回答