JEP 171:围栏内在函数
概述
在 sun.misc.Unsafe
类中添加三个内存排序内在函数。
动机
JVM 没有提供原本未预见到的或在 JSR 133 内存模型规范中未提及的内存排序机制。(但这些机制存在于最近的 C11/C++11 规范中。)其中包括一些在 java.util.concurrent
和其他低级库中使用的结构,这些结构目前依赖于现有内在函数的未记录(且可能是暂时的)属性。
在虚拟机级别添加这些方法允许 JDK 库使用它们以支持 JDK 8 的特性,同时也开启了以后通过新的 java.util.concurrent
API 导出基础功能的可能性。如果即将到来的模块化支持使得其他人无法访问这些方法,那么这对于开发非 JDK 低级库的人来说可能变得至关重要。
描述
这三种方法提供了某些编译器和处理器所需的三种不同类型的内存屏障,以确保特定的访问(加载和存储)不会被重新排序。实际上,它们的效果与现有的 getXXXVolatile
、putXXXVolatile
和 putOrderedXXX
方法相同,只是它们并不实际执行访问操作,而是仅确保顺序性。然而,它们在概念上有一点不同:根据当前的 Java 内存模型(JMM),某些语言级别的 volatile
使用可能与某些非 volatile
变量的使用发生重排序。但在这里这是不允许的。(在当前的内建函数中也不允许,但这是基于内建函数的 volatile
访问与基于语言的 volatile
访问之间一个未记录的区别。)
这三种方法是:
/**
* Ensures lack of reordering of loads before the fence
* with loads or stores after the fence.
*/
void loadFence();
/**
* Ensures lack of reordering of stores before the fence
* with loads or stores after the fence.
*/
void storeFence();
/**
* Ensures lack of reordering of loads or stores before the fence
* with loads or stores after the fence.
*/
void fullFence();
虽然这三种方法没有任何“不安全”的地方,但按照惯例,Unsafe
目前包含了用于每次使用的 volatile 和原子操作的相关方法,因此似乎是这些方法的最佳归宿。
Javadoc 可以更加严谨一些,但无法用 Java 内存模型(JMM)来具体说明,因为它并未涵盖“每次使用的易变性”。因此,将它们保留为这种简单的形式,可能是向目标用户传达意图的最佳方式。此外,由于可能存在现有的使用依赖关系,同时明确表达并略微弱化现有易变访问内在函数所需的最低重排序属性,这大概率是不可能的。然而,当需要时,围栏内在函数的存在允许用户明确无误地获得这些效果。
热点实现
这三种方法可以通过现有的 acquire
、release
和 volatile
栅栏方法来实现,这些方法在 c1、c2 以及解释器/运行时中均可用。此外,在适用的情况下,还可以通过抑制内部编译器的重排序和值重用来实现。这不需要新的底层功能,但仍然需要添加并调整散布在 Hotspot 中支持新内联函数所需的代码。忽略这些细节,以下是一个实现草图:
对于 c2,实现相当于在类似 inline_unsafe_access
的方法中省略所有访问代码,仅保留基于内存的屏障生成,以及一个内部的 CPUOrder
屏障,该屏障会在优化期间禁用重排序。(在 fullFence
的情况下,还需谨慎地包含一个获取屏障(acquire fence)与完整的 volatile
屏障,以覆盖现有 c2 代码可能依赖 MemBarAcquire
的存在来检测相对于加载的 volatile 属性的情况。)
loadFence: {
insert_mem_bar(Op_MemBarCPUOrder);
insert_mem_bar(Op_MemBarAcquire);
}
storeFence: {
insert_mem_bar(Op_MemBarCPUOrder);
insert_mem_bar(Op_MemBarRelease);
}
fullFence: {
insert_mem_bar(Op_MemBarCPUOrder);
insert_mem_bar(Op_MemBarAcquire);
insert_mem_bar(Op_MemBarVolatile);
}
对于 c1,可以使用 LIRGenerator
操作来定义新节点。
loadFence: { if (os::is_MP()) __ membar_acquire(); }
storeFence: { if (os::is_MP()) __ membar_release(); }
fullFence: { if (os::is_MP()) __ membar(); }
此外,对于所有三个选项,在 ValueMap
中禁用 GVN:
xxxxxFence: { kill_memory(); }
而对于 C++ 运行时版本(在 prims/unsafe.cpp
中),通过现有的 OrderAccess
方法来实现:
loadFence: { OrderAccess::acquire(); }
storeFence: { OrderAccess::release(); }
fullFence: { OrderAccess::fence(); }
测试
Aleksey Shipilev 最近为严苛测试 volatiles 和 atomics 而设置的测试基础设施很容易进行调整,以获得与 volatiles 相比,fence-separated access 的相同覆盖率。
风险与假设
我们假设 Oracle 的工程师们会继续协助将 Nashorn 集成到 JDK 8 中。