Skip to content

JEP 171: Fence Intrinsics | Fence 内置函数

摘要

sun.misc.Unsafe 类添加三个内存顺序原语。

动机

JVM 没有提供广告机制来提供最初或 JSR 133 内存模型规范中未设想的内存顺序。(但这些机制在最近的 C11/C++11 规范中是存在的。)这包括 java.util.concurrent 和其他低级库中使用的一些构造,这些构造目前依赖于现有原语的未记录(且可能是临时的)属性。

在 VM 级别添加这些方法允许 JDK 库支持 JDK 8 特性,同时也为日后通过新的 java.util.concurrent API 导出基础功能提供了可能性。如果即将推出的模块化支持使得这些方法无法被其他人访问,那么这对于开发非 JDK 低级库的人员来说可能会变得至关重要。

描述

这三个方法提供了某些编译器和处理器需要的三种不同类型的内存屏障,以确保特定的访问(加载和存储)不会重新排序。实际上,它们的效果与现有的 getXXXVolatileputXXXVolatileputOrderedXXX 方法相同,只是它们实际上并不执行访问操作;它们只是确保顺序。然而,它们在概念上有一个不同之处:根据当前的 JMM(Java 内存模型),volatile 的某些语言级使用可能与非易失性变量的某些使用重新排序。但在这里是不允许的。(在当前的原语中也不允许,但这是基于原语与基于语言的易失性访问之间未记录的区别。)

这三个方法是:

java
/**
 * 确保在屏障之前的加载操作不会与屏障之后的加载或存储操作重新排序。
 */
void loadFence();

/**
 * 确保在屏障之前的存储操作不会与屏障之后的加载或存储操作重新排序。
 */
void storeFence();

/**
 * 确保在屏障之前的加载或存储操作不会与屏障之后的加载或存储操作重新排序。
 */
void fullFence();

虽然这三个方法本身并没有任何“不安全”之处,但按照惯例,Unsafe 目前包含用于每次使用的易失性和原子操作的相关方法,因此似乎是这些方法的最佳归属。

尽管 Javadoc 不能根据 JMM 进行更严格的说明,因为 JMM 不包括“每次使用的易失性”,但将它们保留在这种简单形式中可能最能向目标用户传达意图。此外,由于可能存在现有的使用依赖关系,因此同时明确并稍微削弱现有易失性访问原语所需的最小重新排序属性可能是不可能的。然而,内存屏障原语的存在允许用户在需要时明确获得这些效果。

Hotspot 实现

这三个方法可以通过现有的 acquirereleasevolatile 屏障方法来实现,这些方法在 c1、c2 和解释器 / 运行时中都是可用的。此外,在适用的情况下,还需要抑制内部编译器的重新排序和值重用。这不需要新的底层功能,但仍然需要向 Hotspot 中散布的新原语相关的代码添加和适配。这里省略了这些,只提供一个实现概要:

对于 c2,实现方法是省略 inline_unsafe_access 等方法中的所有访问代码,只保留基于内存的屏障的生成,以及一个内部 CPUOrder 屏障,该屏障在优化期间禁用重新排序。(在 fullFence 的情况下,还谨慎地包含了一个获取屏障以及完整的 volatile 屏障,以覆盖现有 c2 代码可能依赖于 MemBarAcquire 的存在来检测加载操作的易失性的可能性。)

cpp
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 操作的新节点:

cpp
loadFence:  { if (os::is_MP()) __ membar_acquire(); }
storeFence: { if (os::is_MP()) __ membar_release(); }
fullFence:  { if (os::is_MP()) __ membar(); }

此外,对于这三个方法,在 ValueMap 中禁用 GVN(全局值编号):

cpp
xxxxxFence: { kill_memory(); }

对于 C++ 运行时版本(在 prims/unsafe.cpp 中),通过现有的 OrderAccess 方法实现:

cpp
loadFence:  { OrderAccess::acquire(); }
storeFence: { OrderAccess::release(); }
fullFence:  { OrderAccess::fence(); }

测试

Aleksey Shipilev 最近为折磨测试易失性和原子性而建立的测试基础设施可以很容易地调整,以获得对屏障分隔访问与易失性相同的覆盖范围。

风险和假设

我们假设 Oracle 工程师将继续协助将其集成到 JDK 8 中。