Module java.base

Package java.lang.foreign


package java.lang.foreign

提供对Java运行时之外的内存和函数的低级访问。

外部内存访问

支持外部内存访问的主要抽象是 MemorySegment预览,它模拟了一个连续的内存区域,驻留在Java堆内或外。内存段通常使用一个 Arena预览 进行分配,该接口控制支持其分配的内存段的生命周期。内存段的内容可以使用 memory layout预览 描述,该接口提供基本操作来查询大小、偏移和对齐约束。内存布局还提供了一种替代、更抽象的方式,通过 var handles预览 访问内存段,这些 var handles 可以使用 layout paths 计算。例如,要分配一个足够大以容纳 10 个原始类型 int 值的堆外内存区域,并将其填充为从 09 的值,我们可以使用以下代码:

try (Arena arena = Arena.ofConfined()) {
    MemorySegment segment = arena.allocate(10 * 4);
    for (int i = 0 ; i < 10 ; i++) {
        segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
    }
}
这段代码创建了一个 本机 内存段,即由堆外内存支持的内存段;段的大小为 40 字节,足够存储 10 个原始类型 int 值。本机段使用一个 受限制的 arena预览 进行分配。因此,对本机段的访问仅限于当前线程(创建 arena 的线程)。此外,当 arena 关闭时,本机段将失效,并且其支持的内存区域将被释放。请注意使用 try-with-resources 结构:这种习惯用法确保在块结束时将释放支持本机段的堆外内存区域,根据《Java语言规范》第 14.20.3 节中描述的语义。

内存段在内存访问方面提供了强大的安全保证。首先,在访问内存段时,将验证访问坐标(在访问时),以确保访问不会发生在访问操作使用的内存段边界之外的任何地址。我们称这种保证为 空间安全;换句话说,对内存段的访问是进行边界检查的,就像数组访问一样,如《Java语言规范》第 15.10.4 节中所述。

此外,为了防止在释放后访问内存区域(即 use-after-free),在访问段时还会验证(在访问时)确保获取它的 arena 尚未关闭。我们称这种保证为 时间安全

空间和时间安全共同确保每个内存访问操作要么成功 - 并访问内存段支持的内存区域内的有效位置 - 要么失败。

外部函数访问

为支持外部函数访问引入的关键抽象是 SymbolLookup预览FunctionDescriptor预览Linker预览。第一个用于查找库中的符号;第二个用于建模外部函数的签名,而第三个用于将外部函数链接为 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").orElseThrow(),
     FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
 );

 try (Arena arena = Arena.ofConfined()) {
     MemorySegment cString = arena.allocateUtf8String("Hello");
     long len = (long)strlen.invokeExact(cString); // 5
 }
在这里,我们获取一个 本机链接器预览 并使用它来 查找预览 标准C库中的 strlen 函数;随后获得一个针对该函数的 downcall method handle,该方法句柄 获得预览。为了成功完成链接,我们必须提供一个描述 strlen 函数签名的 FunctionDescriptor预览 实例。根据这些信息,链接器将唯一确定一系列步骤,将方法句柄调用(此处使用 MethodHandle.invokeExact(java.lang.Object...) 执行)转换为外部函数调用,根据底层平台ABI规定的规则。 Arena预览 类还提供了许多有用的方法与外部代码交互,例如将 Java 字符串 转换预览 为以零结尾的 UTF-8 字符串,如上例所示。

受限方法

此包中的一些方法被视为 受限。受限方法通常用于将本机外部数据和/或函数绑定到可以直接由客户端使用的一流Java API元素。例如,受限方法 MemorySegment.reinterpret(long)预览 可用于创建一个具有相同地址和时间边界但具有提供的大小的新段。这对于调整与本机函数交互时获得的内存段的大小可能很有用。

绑定外部数据和/或函数通常是不安全的,如果执行不正确,可能导致VM崩溃,或者在访问绑定的Java API元素时导致内存损坏。例如,使用 MemorySegment.reinterpret(long)预览 不正确调整本机内存段的大小可能导致JVM崩溃,或者更糟糕的是,在尝试访问调整大小的段时导致静默内存损坏。因此,调用受限方法的代码绝不能传递可能导致将外部数据和/或函数不正确绑定到Java API的参数。

鉴于受限方法的潜在危险,Java运行时在每次调用受限方法时都会在标准错误流上发出警告。可以通过向选定模块授予对受限方法的访问权限来禁用此类警告。可以通过特定于实现的命令行选项或编程方式实现此操作,例如通过调用 ModuleLayer.Controller.enableNativeAccess(java.lang.Module)预览

对于此包中的每个类,除非另有说明,引用类型的方法参数不得为 null,任何 null 参数都将引发 NullPointerException。对于此API的方法,这一事实没有单独记录。

API 注释:
通常的内存模型保证,例如在6.610.4中所述,不适用于访问本机内存段,因为这些段由内存的非堆区支持。
实现注释:
在参考实现中,可以使用命令行选项--enable-native-access=M1,M2, ... Mn授予对特定模块的受限方法的访问权限,其中M1M2... Mn是模块名称(对于未命名模块,可以使用特殊值ALL-UNNAMED)。如果指定了此选项,则只授予对该选项列出的模块的受限方法的访问权限。如果未指定此选项,则对所有模块启用对受限方法的访问权限,但对受限方法的访问将导致运行时警告。
外部规范