【问题标题】:Shenandoah Garbage Collector Load Reference BarriersShenandoah 垃圾收集器负载参考屏障
【发布时间】:2020-09-20 03:04:16
【问题描述】:

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

Shenandoah 2.0 声称这不再是问题,并且通过所谓的加载引用障碍解决了。这到底是怎么回事?

【问题讨论】:

    标签: java garbage-collection jvm shenandoah


    【解决方案1】:

    我假设读者知道什么是障碍以及为什么需要它。非常简短的介绍here is another answer of mine on the topic

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

    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).userRB 代表read barrier)和WB(user).ageWB 代表write barrier)。现在想象一下循环是hot——你将为这么多障碍付出代价。即使在循环执行期间没有 GC 活动,屏障仍然就位,并且必须有代码有条件地检查是否需要执行屏障与否。

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

    这些屏障是维持堆一致性所必需的,因为在疏散阶段内存中有一个对象的两个副本,因此您需要始终保持一致的读取和写入。 一致在这里意味着在Shenandoah 1.0中,读取可能发生在“to-space”或“from-space”(称为“weak to-space invariant”) ,而 write 可能发生在 to-space only


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

    它通过一个相当简单但绝妙的想法实现了这个“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 屏障并移除了另外两个。因此,加载引用障碍会在加载对象时发生,它们在定义站点调用此:在定义站点,而不是在读取或存储到它时,他们在其使用站点调用此 。您可以将其视为在使用aloadgetField(供参考)的位置插入这些障碍。

    【讨论】:

    • 通过阅读您的答案,我在过去 15 分钟内对雪兰多的了解比过去几个小时的谷歌搜索要多。谢谢!
    猜你喜欢
    • 2014-04-21
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 2018-12-30
    • 2019-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多