Shenandoah 自愈障碍
Shenandoah self healing barriers
标题几乎说明了一切 - 这些自我修复障碍是什么,为什么它们在 Shenandoah 2.0 中很重要?
此解释将 piggy-back 用于 and 我试图围绕 Shenandoah 2.0
提出的一些答案。
要真正回答这个问题,我们需要了解 load reference barrier
的实现方式以及 GC cycle
的一般行为方式。
触发某个GC cycle
时,首先选择region垃圾最多的;即:集合中的对象很少(这在将来很重要)。
理解本主题的最简单方法是通过示例。假设这是某个地区现在存在的方案:
refA refB
|
---------
| mark |
---------
| i = 0 |
| j = 0 |
---------
该区域中存在一个对象,并且有两个引用指向它:refA
和 refB
。 GC
开始,这个区域被选为垃圾收集。 同时 应用程序中有活动线程试图通过refA
和refB
访问此对象。由于此对象在某些时候是 alive
,因此需要将其 疏散 到一个新区域(mark-compact
阶段的一部分)。
因此:GC
是 active,同时,我们通过 refA/refB
read。当我们做这个阅读时,我们踩到了load-reference-barrier
,实现了here。请注意它在内部如何具有一些“过滤器”(通过一堆 if/else
语句)。具体来说:
它检查“疏散当前是否正在进行”。这是通过疏散首次开始时设置的线程本地标志来完成的。让我们假设答案是:是的。
检查我们当前操作的对象是否在“collection-set”中。这意味着它当前被标记为活着。假设这也是“是”。
最后的检查是查明这个对象是否已经被“复制”到不同的区域(它被疏散了)。假设答案是“否”,即:obj == fwd
.
此时,发生了一些事情。首先创建一个副本 mark
refA refB
|
-------------- ---------
| forwardee | ---- | mark |
-------------- ---------
| i = 0 | | i = 0 |
| j = 0 | | j = 0 |
--------- ---------
仅在代码的后面,refA
和 refB
才会更新为指向 new(复制的)对象。但这意味着一件有趣的事情。这意味着直到 refA
和 refB
实际上指向新对象,它们当前指向的对象在“集合集中”。所以,如果 GC 处于活动状态,即使 forwardee
已经建立,load-reference-barrier
仍然需要做一些工作。
所以 Shenandoah
背后的 非常 聪明的人说:为什么不更新那里的引用,在 forwardee
建立之后(或者当forwardee
已因其他参考文献而为人所知)? And this is exactly what they did.
假设我们回到最初的绘图:
refA refB
|
---------
| mark |
---------
| i = 0 |
| j = 0 |
---------
再一次,我们“启用”所有过滤器:
有一个线程通过refA
读取
GC 处于活动状态
refA
和refB
后面的物体还活着。
这就是“自我修复障碍”会发生的情况:
refB refA
| |
-------------- ---------
| forwardee | ---- | mark |
-------------- ---------
| i = 0 | | i = 0 |
| j = 0 | | j = 0 |
--------- ---------
区别很明显:refA
被当场移动到 CAS
指向新对象。如果要通过 refA
再次读取 (GC 仍处于活动状态),这将导致更快的 load-reference-barrier 执行。为什么?因为 refA
指向一个对象 而不是 在“集合集”中。
但这也意味着如果我们通过 refB
读取并看到 fwd != obj
- 代码可以执行相同的技巧并更新 refB
第一次阅读发生在 refB
.
据熟悉此事的人说,这提高了性能,我相信他们。
标题几乎说明了一切 - 这些自我修复障碍是什么,为什么它们在 Shenandoah 2.0 中很重要?
此解释将 piggy-back 用于 Shenandoah 2.0
提出的一些答案。
要真正回答这个问题,我们需要了解 load reference barrier
的实现方式以及 GC cycle
的一般行为方式。
触发某个GC cycle
时,首先选择region垃圾最多的;即:集合中的对象很少(这在将来很重要)。
理解本主题的最简单方法是通过示例。假设这是某个地区现在存在的方案:
refA refB
|
---------
| mark |
---------
| i = 0 |
| j = 0 |
---------
该区域中存在一个对象,并且有两个引用指向它:refA
和 refB
。 GC
开始,这个区域被选为垃圾收集。 同时 应用程序中有活动线程试图通过refA
和refB
访问此对象。由于此对象在某些时候是 alive
,因此需要将其 疏散 到一个新区域(mark-compact
阶段的一部分)。
因此:GC
是 active,同时,我们通过 refA/refB
read。当我们做这个阅读时,我们踩到了load-reference-barrier
,实现了here。请注意它在内部如何具有一些“过滤器”(通过一堆 if/else
语句)。具体来说:
它检查“疏散当前是否正在进行”。这是通过疏散首次开始时设置的线程本地标志来完成的。让我们假设答案是:是的。
检查我们当前操作的对象是否在“collection-set”中。这意味着它当前被标记为活着。假设这也是“是”。
最后的检查是查明这个对象是否已经被“复制”到不同的区域(它被疏散了)。假设答案是“否”,即:
obj == fwd
.
此时,发生了一些事情。首先创建一个副本 mark
refA refB
|
-------------- ---------
| forwardee | ---- | mark |
-------------- ---------
| i = 0 | | i = 0 |
| j = 0 | | j = 0 |
--------- ---------
仅在代码的后面,refA
和 refB
才会更新为指向 new(复制的)对象。但这意味着一件有趣的事情。这意味着直到 refA
和 refB
实际上指向新对象,它们当前指向的对象在“集合集中”。所以,如果 GC 处于活动状态,即使 forwardee
已经建立,load-reference-barrier
仍然需要做一些工作。
所以 Shenandoah
背后的 非常 聪明的人说:为什么不更新那里的引用,在 forwardee
建立之后(或者当forwardee
已因其他参考文献而为人所知)? And this is exactly what they did.
假设我们回到最初的绘图:
refA refB
|
---------
| mark |
---------
| i = 0 |
| j = 0 |
---------
再一次,我们“启用”所有过滤器:
有一个线程通过
读取refA
GC 处于活动状态
refA
和refB
后面的物体还活着。
这就是“自我修复障碍”会发生的情况:
refB refA
| |
-------------- ---------
| forwardee | ---- | mark |
-------------- ---------
| i = 0 | | i = 0 |
| j = 0 | | j = 0 |
--------- ---------
区别很明显:refA
被当场移动到 CAS
指向新对象。如果要通过 refA
再次读取 (GC 仍处于活动状态),这将导致更快的 load-reference-barrier 执行。为什么?因为 refA
指向一个对象 而不是 在“集合集”中。
但这也意味着如果我们通过 refB
读取并看到 fwd != obj
- 代码可以执行相同的技巧并更新 refB
第一次阅读发生在 refB
.
据熟悉此事的人说,这提高了性能,我相信他们。