对于那些关注Shenandoah开发的人来说,一个主要的批评是它在每次写和读时都使用了GC barriers,这不是一个大秘密:无论它是参考的还是原始的。
Shenandoah 2.0声称这不再是一个问题,它是通过所谓的负载参考屏障解决的。这到底是怎么回事?
发布于 2020-09-20 03:04:16
我假设读者知道什么是障碍,为什么需要障碍。非常简短的介绍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).user (RB代表read barrier)和WB(user).age (WB代表write barrier)。现在假设循环是hot --你将为这么多的障碍付出代价。即使在执行循环期间没有GC活动,屏障仍然存在,并且必须有有条件地检查是否需要执行屏障的代码。
长话短说:这些障碍绝不是自由的。
为了保持堆的一致性,需要设置这些屏障,因为在疏散阶段,内存中有两个对象的副本,您需要始终保持读和写的一致性。这里一贯的意思是,在Shenandoah 1.0中,读取可能发生在“从空间到空间”或“从空间到空间”(称为“弱到空间不变”),而写入可能只发生在to-space中。
Shenandoah 2.0说,它将确保所谓的“空间不变”(相对于以前的弱不变)。基本上-它说所有的写和读将发生在/进入“到-空间”。在疏散过程中,物体有两个副本:一个在旧区域(称为“从空间”),另一个在新区域(称为“到空间”)。
它实现了这个“空间”不变与一个相当简单,但聪明的想法。它确保最初加载的对象确实从“到空间”加载,而不是在发生writes的情况下使用屏障。这是通过负载参考屏障完成的。通过重构前面的示例来理解这一点要简单得多:
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屏障,并移除了另外两个屏障。所以,当一个对象被加载时,负载引用屏障就会发生,他们称它为:在定义站点,而不是在读取或存储对象时,在它们的使用站点调用它。您可以这样想,就像在使用aload和getField (用于引用)的地方插入了这些屏障一样。
https://stackoverflow.com/questions/63975139
复制相似问题