我假设读者知道什么是障碍以及为什么需要它。非常简短的介绍这是我关于该主题的另一个答案 https://stackoverflow.com/questions/59044616/whats-the-mark-compact-algorithm-used-by-hotspot/59141137#59141137.
为了正确理解这一点,我们需要首先看看最初的问题到底出在哪里。让我们举一个相当简单的例子:
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
}
}
这个想法不是为了展示correct示例,但是一个示例:
-
a read (user.zip;
)
-
a write (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
) and WB(user).age
(WB
代表write barrier
)。现在想象一下循环是hot
- 你将为如此多的障碍付出代价。即使在循环执行期间没有 GC 活动,障碍也是如此still到位并且必须有代码有条件的检查是否需要执行屏障。
长话短说:无论如何,这些障碍都不是免费的。
需要这些屏障来保持堆一致性,因为有two内存中对象的副本疏散阶段,你需要始终保持一致的读写。始终如一这里的意思是在Shenandoah 1.0
a read可能是从“到空间”或“从空间”(称为“弱到空间不变量”)发生的,而write可能发生于to-space
only.
Shenandoah 2.0
说它将确保所谓的“空间不变量”(与之前的相反)weak一)。基本上 - 它表示所有写入和读取都将发生在“to-space”中/“to-space”中。在疏散过程中,对象有两份副本:一份位于旧区域(称为“from-space”),一份位于新区域(称为“to-space”)。
它通过一个相当简单但绝妙的想法实现了这种“空间”不变量。而不是使用障碍writes
发生这种情况时,它确保最初加载的对象肯定是从“to-space”加载的。这是通过以下方式完成的负载参考屏障。通过重构前面的示例来理解这一点要简单得多:
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
and getField
(供参考)使用。