Shenandoah
对于看过发展的人来说,一个主要的批评是它对每一次写入和读取都使用GC barriers
:无论是参考还是原始,这并不是什么大秘密。
Shenandoah 2.0
声称这不再是问题,并且可以通过所谓的负载参考障碍来解决。这到底是怎么回事?
Shenandoah
对于看过发展的人来说,一个主要的批评是它对每一次写入和读取都使用GC barriers
:无论是参考还是原始,这并不是什么大秘密。
Shenandoah 2.0
声称这不再是问题,并且可以通过所谓的负载参考障碍来解决。这到底是怎么回事?
我假设读者知道什么是障碍以及为什么需要它。对于一个非常简短的介绍,这里是我关于该主题的另一个答案。
为了正确理解这一点,我们需要首先看看最初的问题到底在哪里。让我们举一个相当简单的例子:
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.0
to-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 。您可以将其视为将这些障碍插入到使用aload
和getField
(用于引用)的位置。