Package java.lang.foreign
提供对Java运行时之外的内存和函数的低级访问。
外部内存访问
支持外部内存访问的主要抽象是 MemorySegment
预览,它模拟了一个连续的内存区域,驻留在Java堆内或外。内存段通常使用一个 Arena
预览 进行分配,该接口控制支持其分配的内存段的生命周期。内存段的内容可以使用 memory layout
预览 描述,该接口提供基本操作来查询大小、偏移和对齐约束。内存布局还提供了一种替代、更抽象的方式,通过 var handles预览 访问内存段,这些 var handles 可以使用 layout paths 计算。例如,要分配一个足够大以容纳 10 个原始类型 int
值的堆外内存区域,并将其填充为从 0
到 9
的值,我们可以使用以下代码:
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);
}
}
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
}
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的方法,这一事实没有单独记录。
-
ClassDescription预览。用于模拟某个内存区域地址的值布局。预览。一个区域控制本机内存段的生命周期,提供灵活的分配和及时的释放。预览。函数描述符模拟外部函数的签名。预览。一个复合布局,是多个异构成员布局的聚合。预览。链接器提供从Java代码访问外部函数的功能,并从外部函数访问Java代码的功能。预览。链接器选项用于向链接请求提供附加参数。预览。内存布局描述内存段的内容。预览。布局路径中的一个元素。预览。内存段提供对连续内存区域的访问。预览。作用域模拟与其关联的所有内存段的生命周期。预览。填充布局。预览。表示给定元素布局的同质重复的复合布局。预览。其成员布局依次排列的组布局。预览。符号查找检索一个或多个库中符号的地址。预览。其成员布局从相同的起始偏移量开始排列的组布局。预览。模拟基本数据类型值的布局。预览。其载体为
boolean.class
的值布局。预览。其载体为byte.class
的值布局。预览。其载体为char.class
的值布局。预览。其载体为double.class
的值布局。预览。其载体为float.class
的值布局。预览。其载体为int.class
的值布局。预览。其载体为long.class
的值布局。预览。其载体为short.class
的值布局。