Foreign-Memory Access API外部内存API
引入API,使Java程序能够安全有效地访问Java堆之外的外部内存
发展历史:
JEP 383: Foreign-Memory Access API (Second Incubator) java15
JEP 393: Foreign-Memory Access API (Third Incubator) java16
JEP 389: Foreign Linker API (Incubator) java16
JEP 412: Foreign Function & Memory API (Incubator) java17
JEP 419: Foreign Function & Memory API (Second Incubator) java18
JEP 424: Foreign Function & Memory API (Preview) java19
JEP 434: Foreign Function & Memory API (Second Preview) java20
JEP 442: Foreign Function & Memory API (Third Preview) java21
JEP 454: Foreign Function & Memory API java22
JEP 472: Prepare to Restrict the Use of JNI java24
对外内存访问API引入了三个主要的抽象:MemorySegment、MemoryAddress和MemoryLayout。
MemorySegment用于对具有给定空间和时间边界的连续内存区域进行建模。MemoryAddress可以看作是段内的偏移。最后,MemoryLayout是对内存段内容的程序化描述。
内存段可以从各种来源创建,例如本机内存缓冲区、Java数组和字节缓冲区(直接或基于堆)。例如,可以按如下方式创建本机内存段:
try(MemorySegment段=MemorySegment.reallocateNative(100)){
...
}
这将创建一个与大小为100字节的本机内存缓冲区关联的内存段。
Memory segments在空间上是有界限的,它们有下限和上限。任何试图使用该段访问这些边界之外的内存的尝试都将导致异常。正如使用try with resource构造所证明,segments在时间上也是有界的,它们被创建、使用,然后在不再使用时关闭。关闭一个段总是一个显式的操作,可能会导致额外的副作用,例如与该段相关的内存的释放。任何访问已关闭内存段的尝试都会导致异常。因此空间和时间安全检查对于保证内存访问API的安全性至关重要,来保证没有硬JVM崩溃。
通过获取内存访问变量句柄,可以实现对与段关联的内存的解引用。这些特殊的var句柄至少有一个MemoryAddress类型的强制访问坐标,这是发生解引用的地址。它们是使用MemoryHandles类中的工厂方法获得的。例如,要设置本机段的元素,我们可以使用内存访问var句柄,如下所示
VarHandle intHandle = MemoryHandles.varHandle(int.class,
ByteOrder.nativeOrder());
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
MemoryAddress base = segment.baseAddress();
for (int i = 0; i < 25; i++) {
intHandle.set(base.addOffset(i * 4), i);
}
}
内存访问var句柄还可以获取一个或多个long类型的额外访问坐标,以支持更复杂的寻址方案,如多维索引访问。这种内存访问var句柄通常是通过调用一个或多个组合子方法获得的,这些方法也在MemoryHandles类中定义。例如,设置本机段元素的一种更直接的方法是通过索引内存访问句柄,其构造如下:
VarHandle intHandle = MemoryHandles.varHandle(int.class,
ByteOrder.nativeOrder());
VarHandle intElemHandle = MemoryHandles.withStride(intHandle, 4);
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
MemoryAddress base = segment.baseAddress();
for (int i = 0; i < 25; i++) {
intElemHandle.set(base, (long) i, i);
}
}
这有效地允许对原本平坦的内存缓冲区进行丰富的多维寻址。
为了增强API的表达能力,并减少对显式数字计算(如上面示例中的计算)的需要,可以使用MemoryLayout API以编程方式描述内存段的内容。例如,上述示例中使用的本机内存段的布局可以按以下方式描述:
SequenceLayout intArrayLayout= MemoryLayout.ofSequence(25,MemoryLayout.ofValueBits(32,ByteOrder.nativeOrder()));
这创建了一个序列内存布局,其中给定的元素布局(32位值)重复25次。一旦我们有了内存布局,我们就可以摆脱代码中的所有手动数值计算,并简化所需内存访问var句柄的创建,如下例所示:
SequenceLayout intArrayLayout= MemoryLayout.ofSequence(25,MemoryLayout.ofValueBits(32,ByteOrder.nativeOrder()));
VarHandle intElemHandle= intArrayLayout.varHandle(int.class,PathElement.sequenceElement());
try (MemorySegment segment = MemorySegment.allocateNative(intArrayLayout)) {
MemoryAddress base = segment.baseAddress();
for (int i = 0; i < intArrayLayout.elementCount().getAsLong(); i++) {
intElemHandle.set(base, (long) i, i);
}
}
在这个例子中,布局实例通过创建布局路径来驱动内存访问var句柄的创建,该路径用于从复杂的布局表达式中选择嵌套布局。布局实例还驱动本机内存段的分配,该分配基于从布局中导出的大小和对齐信息。前面示例中的循环常量已被序列布局的元素计数替换。
外部存储器访问API最初将作为一个孵化模块提供,名为jdk.incubator.foreign,在一个同名的包中。