JEP 370:JDK 14的外部内存访问API | Perforce旗下JRebel

作者:API传播员 · 2025-10-30 · 阅读时间:5分钟

随着JDK发布节奏的加快,几乎每周都会有新的JDK增强提案(JEP)发布。本文将聚焦于JEP 370:外部内存访问API(Foreign-Memory Access API,孵化器模块),这是JDK 14中的一项新特性。我们将深入探讨该API的工作原理,并详细介绍其三个核心抽象:内存段(MemorySegment)、内存地址(MemoryAddress)和内存布局(MemoryLayout)。


JEP 370:外部内存访问API(孵化器模块)

JEP 370的核心目标是为Java程序提供访问Java堆之外外部存储器的能力。根据JEP的说明,引入这一API的主要原因包括:

  1. 支持跨多个进程共享内存。
  2. 通过将文件映射到内存,实现内存内容的序列化和反序列化。

尽管这一提案的动机明确,但目前的实现尚未提供一种在进程间轻松共享内存的方法。


外部内存访问API是否带来了新功能?

简单来说:没有。许多现有的Java库已经能够访问外部内存,因此JDK 14中新增的外部内存访问API并未直接为这些库增加新功能。然而,该API提供了一种更受支持且更具互操作性的方法,能够在JVM中以更安全的方式处理外部内存,而无需依赖外部库。

在现有的JDK中,类似功能通常通过以下方式实现:

  • ByteBuffer API:自Java 1.4以来一直存在。
  • JNI(Java Native Interface):需要为每种架构实现本地代码。
  • Unsafe API:允许访问任意内存位置,但不受官方支持。

相比之下,外部内存访问API的引入显得尤为必要。


JEP 370与巴拿马项目的关系

JEP 370是巴拿马项目的一部分,该项目旨在提升JVM与非Java API之间的互操作性。通过引入一种可靠且高效的方式与外部内存交互,JEP 370进一步推动了巴拿马项目的目标,同时为未来增强外部函数接口奠定了基础。


外部内存访问API的工作原理

外部内存访问API引入了三个核心抽象:

  • MemorySegment:表示一段连续的内存区域。
  • MemoryAddress:表示内存段中的具体位置。
  • MemoryLayout:以语言中立的方式定义内存段的布局。

由于该API位于孵化器模块中,使用时需要在module-info.java文件中添加jdk.incubator.foreign模块,或者通过--add-modules JVM参数启用。


使用内存段(MemorySegment)

内存段是对连续内存区域的抽象,既可以由堆内存支持,也可以由堆外内存支持。通过工厂方法,可以创建由基元数组、ByteBuffer或文件的内存映射区域支持的内存段,或者分配新的堆外内存段。

为了确保资源的及时释放,MemorySegment实现了AutoCloseable接口,因此可以与try-with-resources一起使用。当内存段被关闭后,再次访问将抛出IllegalStateException

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
    // 使用内存段
}

内存段绑定到创建时的线程。如果需要在其他线程中使用,需要通过acquire()方法获取一个新段。在关闭所有获取的内存段之前,原始内存段无法关闭。


使用内存地址(MemoryAddress)

MemoryAddress表示内存段中的偏移量,通常通过调用segment.baseAddress()方法获取。尽管内存地址是线程安全的,但在多线程环境中使用时,仍需先获取关联的内存段。

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
    MemoryAddress address = segment.baseAddress();
}

内存地址通常与VarHandle配合使用,以访问底层数据。例如,以下代码展示了如何以大端字节序写入整数数据:

VarHandle handle = MemoryHandles.varHandle(int.class, ByteOrder.BIG_ENDIAN);
for (int i = 0; i < 32; i++) {
    handle.set(segment.baseAddress().addOffset(i * Integer.BYTES), 1 << i);
}

使用内存布局(MemoryLayout)

通过偏移量直接访问内存段可能并不直观。MemoryLayout提供了一种语言中立的方式来描述内存布局,例如声明小端字节序的32位值或本机顺序的64位值。

SequenceLayout layout = MemoryLayout.sequenceLayout(10, MemoryLayouts.JAVA_INT);

更复杂的数据结构也可以通过MemoryLayout描述。例如,一个包含两个整数的点结构可以定义为:

GroupLayout pointLayout = MemoryLayout.structLayout(
    MemoryLayouts.JAVA_INT.withName("x"),
    MemoryLayouts.JAVA_INT.withName("y")
);

此外,内存布局支持嵌套。例如,一个三角形可以表示为三个点的序列:

SequenceLayout triangleLayout = MemoryLayout.sequenceLayout(3, pointLayout);

使用VarHandle访问单个元素

VarHandle允许通过内存布局访问具体的内存元素。例如,以下代码展示了如何访问三角形中第二个点的xy坐标:

VarHandle varX = triangleLayout.varHandle(int.class,
    MemoryLayout.PathElement.sequenceElement(),
    MemoryLayout.PathElement.groupElement("x")
);
VarHandle varY = triangleLayout.varHandle(int.class,
    MemoryLayout.PathElement.sequenceElement(),
    MemoryLayout.PathElement.groupElement("y")
);

varX.set(triangleSegment.baseAddress(), 1, 10);
varY.set(triangleSegment.baseAddress(), 1, 30);

展望未来

外部内存访问API为描述和访问内存段提供了更有序的工具。未来的改进可能包括支持将记录或内联类直接映射到MemoryLayout,从而简化从内存映射文件读取和写入结构化数据的过程。


总结

JEP 370通过引入外部内存访问API,为Java提供了一种更高效、更安全的方式来与外部内存交互。这不仅提升了JVM的互操作性,也为未来的功能扩展奠定了基础。随着该API的不断完善,Java开发者将能够更轻松地处理复杂的内存操作。

原文链接: https://www.jrebel.com/blog/jep-370-foreign-memory-access-api