包 java.lang.foreign
提供对 Java 运行时之外的内存和函数的低级访问。
外部内存访问
为支持外部内存访问而引入的主要抽象是MemorySegment PREVIEW ,它模拟一个连续的内存区域,驻留在 Java 堆的内部或外部。内存段的内容可以使用memory layout 来描述PREVIEW ,它提供了查询大小、偏移量和对齐约束的基本操作。内存布局还为 访问内存段 使用 变量句柄 提供了另一种更抽象的方式PREVIEW ,可以使用 layout paths 计算。例如,要分配一个足够大的堆外内存区域以容纳原始类型 int 的 10 个值,并用范围从 0 到 9 的值填充它,我们可以使用以下代码:
MemorySegment segment = MemorySegment.allocateNative(10 * 4, SegmentScope.auto());
for (int i = 0 ; i < 10 ; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
int 的 10 个值。该段与 自动范围 相关联PREVIEW .这意味着支持该段的堆外内存区域由垃圾收集器自动管理。因此,支持本机段的堆外内存将在某个未指定的点释放 after 该段变为 遥不可及 。这类似于通过 ByteBuffer.allocateDirect(int) 创建的直接缓冲区所发生的情况。也可以更直接地管理分配的本机段的生命周期,如后面的部分所示。
在一个循环中,我们然后初始化内存段的内容;请注意访问方法PREVIEW 接受 值布局PREVIEW ,它指定大小、对齐约束、字节顺序以及与访问操作关联的 Java 类型(在本例中为 int )。更具体地说,如果我们将内存段视为一组 10 个相邻槽,s[i],其中 0 <= i < 10,其中每个槽的大小恰好是 4 个字节,上面的初始化逻辑将设置每个槽,以便 s[i] = i,再次是 0 <= i < 10。
确定性重新分配
当编写操作内存段的代码时,尤其是如果由驻留在 Java 堆之外的内存支持时,与内存段关联的资源在该段不再使用时及时释放通常是至关重要的。因此,在某些情况下,等待垃圾收集器确定段为 遥不可及 并不是最佳选择。在这些假设下运行的客户端可能希望以编程方式释放支持内存段的内存。这可以使用Arena 来完成PREVIEW 抽象,如下图:
try (Arena arena = Arena.openConfined()) {
MemorySegment segment = arena.allocate(10 * 4);
for (int i = 0 ; i < 10 ; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
}
安全
该 API 在内存访问方面提供了强大的安全保证。首先,当取消引用一个内存段时,访问坐标被验证(在访问时),以确保访问不会发生在位于outside访问操作使用的内存段边界的任何地址。我们称此保证为spatial safety;换句话说,对内存段的访问是边界检查的,与数组访问的方式相同,如第 15.10.4 节所述Java 语言规范.由于用 arena 创建的内存段可能会变得无效(见上文),段也会被验证(在访问时)以确保与正在访问的段关联的范围仍然有效。我们称此担保为temporal safety。空间和时间安全共同确保每个内存访问操作成功 - 并访问支持内存段的内存区域内的有效位置 - 或失败。
外部函数访问
为支持外部函数访问而引入的关键抽象是SymbolLookup PREVIEW , FunctionDescriptor PREVIEW 和Linker PREVIEW .第一个用于在库中查找符号;第二个用于建模外部函数的签名,而第三个提供链接功能,允许将外部函数建模为MethodHandle 实例,以便客户端可以直接在 Java 中执行外部函数调用,而无需 C/C++ 代码的中间层(与 Java 本机接口 (JNI) 的情况一样)。
例如,要在 Linux x64 平台上使用 C 标准库函数strlen 计算字符串的长度,我们可以使用以下代码:
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").get(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.openConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello");
long len = (long)strlen.invoke(cString); // 5
}
strlen 符号; downcall method handle 以所述符号为目标随后是 获得PREVIEW .要成功完成链接,我们必须提供一个FunctionDescriptor PREVIEW 例如,描述 strlen 函数的签名。根据此信息,链接器将根据底层平台的 ABI 指定的规则,唯一地确定将方法句柄调用(此处使用 MethodHandle.invoke(java.lang.Object...) 执行)转换为外部函数调用的步骤序列。 Arena PREVIEW 类还提供了许多与外部代码交互的有用方法,例如转换PREVIEW Java 字符串转换为以零结尾的 UTF-8 字符串,如上例所示。
Upcalls
Linker PREVIEW interface 还允许客户端将现有方法句柄(可能指向 Java 方法)转换为内存段,以便 Java 代码可以有效地传递给其他外部函数。例如,我们可以编写一个比较两个整数值的方法,如下所示:
class IntComparator {
static int intCompare(MemorySegment addr1, MemorySegment addr2) {
return addr1.get(ValueLayout.JAVA_INT, 0) -
addr2.get(ValueLayout.JAVA_INT, 0);
}
}
FunctionDescriptor intCompareDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.asUnbounded(),
ValueLayout.ADDRESS.asUnbounded());
MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.class,
"intCompare",
intCompareDescriptor.toMethodType());
FunctionDescriptor PREVIEW 例如,这次描述了我们要创建的函数指针的签名。描述符可用于derivePREVIEW 一种方法类型,可用于查找 IntComparator.intCompare 的方法句柄。
现在我们有了一个方法句柄实例,我们可以使用 Linker 将它变成一个新的函数指针PREVIEW 接口,如下:
SegmentScope scope = ...
MemorySegment comparFunc = Linker.nativeLinker().upcallStub(
intCompareHandle, intCompareDescriptor, scope);
);
FunctionDescriptor PREVIEW 然后将上一步中创建的实例用于createPREVIEW 一个新的 upcall 存根;函数描述符中的布局允许链接器确定允许外部代码根据底层平台 ABI 指定的规则调用 intCompareHandle 存根的步骤序列。 upcall存根的生命周期与scope相关联PREVIEW 创建上行存根时提供。 MemorySegment 提供了相同的作用域PREVIEW 该方法返回的实例。
受限方法
这个包中的一些方法被认为是 restricted 。受限方法通常用于将本地外部数据和/或函数绑定到一流的 Java API 元素,然后客户端可以直接使用这些元素。例如限制方法MemorySegment.ofAddress(long, long, SegmentScope) PREVIEW 可用于从本地地址创建具有给定空间边界的新段。
绑定外部数据和/或函数通常是不安全的,如果操作不当,可能会在访问绑定的 Java API 元素时导致 VM 崩溃或内存损坏。例如,在 MemorySegment.ofAddress(long, long, SegmentScope) 的情况下PREVIEW ,如果提供的空间边界不正确,则该方法返回的段的客户端可能会导致 VM 崩溃,或者在尝试访问所述段时损坏内存。由于这些原因,对于调用受限方法的代码来说,永远不要传递可能导致外部数据和/或函数错误绑定到 Java API 的参数是至关重要的。
考虑到受限方法的潜在危险,每次调用受限方法时,Java 运行时都会在标准错误流上发出警告。可以通过向选定模块授予对受限方法的访问权限来禁用此类警告。这可以通过特定于实现的命令行选项或以编程方式完成,例如通过调用 ModuleLayer.Controller.enableNativeAccess(java.lang.Module) PREVIEW .
对于这个包中的每个类,除非另有说明,否则任何引用类型的方法参数都不能为 null,并且任何 null 参数都将引发 NullPointerException 。这个事实没有单独记录这个 API 的方法。
-
接口类描述预览。arena 控制内存段的生命周期,提供灵活的分配和及时的释放。预览。函数描述符模拟外部函数的签名。预览。聚合多个 member layouts 的复合布局。预览。链接器提供从 Java 代码访问外部函数,以及从外部函数访问 Java 代码。预览。链接器选项用于指示链接器的附加链接要求,除了函数描述符所描述的内容之外。预览。一个链接器选项,用于在调用与向下调用方法句柄关联的外部函数之后立即保存部分执行状态,然后它可以被运行时覆盖,或通过常规方式读取。预览。内存布局描述了内存段的内容。预览。layout path 中的一个元素。预览。内存段提供对连续内存区域的访问。预览。填充布局。预览。段作用域控制对内存段的访问。预览。表示给定 element layout 重复的复合布局。预览。一个组布局,其成员布局一个接一个地布置。预览。symbol lookup 检索一个或多个库中符号的地址。预览。一种组布局,其成员布局以相同的起始偏移量布置。预览。用于创建和操作变量参数列表的帮助程序类,在功能上类似于 C
va_list。预览。对基本数据类型的值建模的布局。预览。载体为MemorySegment.class的值布局。预览。载体为boolean.class的值布局。预览。载体为byte.class的值布局。预览。载体为char.class的值布局。预览。载体为double.class的值布局。预览。载体为float.class的值布局。预览。载体为int.class的值布局。预览。载体为long.class的值布局。预览。载体为short.class的值布局。