Project Panama(Foreign Function & Memory API)详解
目录
一、Project Panama概述与动机
1.1 什么是Project Panama
Project Panama 是 OpenJDK 的一个子项目,旨在改善 JVM 与非 Java 代码之间的互操作性。其核心成果是 Foreign Function & Memory API(外部函数与内存API),于 JDK 22 正式发布(JEP 454)。
┌─────────────────────────────────────────────────────┐
│ Java Application │
├─────────────────────────────────────────────────────┤
│ Foreign Function & Memory API │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ Arena │ │ Memory │ │ Linker │ │
│ │ │ │ Segment │ │ (Downcall/Upcall)│ │
│ └──────────┘ └──────────┘ └───────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Native Code (C/C++/Rust) │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ libc │ │ libm │ │ custom .so/.dll │ │
│ └──────────┘ └──────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────┘
1.2 演进历程
| Incubator 1 | JDK 14 | JEP 370 | 孵化 |
| Incubator 2 | JDK 15 | JEP 383 | 孵化 |
| Incubator 3 | JDK 16 | JEP 393 | 孵化 |
| Incubator 4 | JDK 17 | JEP 412 | 孵化 |
| Preview 1 | JDK 19 | JEP 424 | 预览 |
| Preview 2 | JDK 20 | JEP 434 | 预览 |
| Preview 3 | JDK 21 | JEP 442 | 预览 |
| Final | JDK 22 | JEP 454 | 正式 |
1.3 核心设计目标
- 易用性:纯Java API,无需编写C/C++胶水代码
- 安全性:默认启用边界检查,内存生命周期可控
- 性能:接近JNI甚至超越,支持JIT优化
- 通用性:支持任意本地库,不局限于特定平台
1.4 模块与包
// 所需模块(JDK 22+默认可用)
// java.base 模块中的 java.lang.foreign 包
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
二、JNI的问题
2.1 开发复杂度高
// JNI方式:需要编写C胶水代码
// 1. 先定义Java类
// public class Native { public native int add(int a, int b); }
// 2. 生成头文件:javac -h . Native.java
// 3. 实现C代码:
#include "Native.h"
JNIEXPORT jint JNICALL Java_Native_add
(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
// 4. 编译为动态库
// gcc -shared -fPIC -o libnative.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux Native.c
// JNI方式:Java端加载
public class Native {
static {
System.loadLibrary("native"); // 依赖java.library.path
}
public native int add(int a, int b);
}
2.2 安全性问题
// JNI中的危险操作——无任何保护
JNIEXPORT void JNICALL Java_Native_process
(JNIEnv *env, jobject obj, jlong ptr, jint len) {
char *buffer = (char *)ptr;
// 没有边界检查!可能缓冲区溢出
buffer[len + 100] = '\\0'; // 越界写入 → 段错误 → JVM崩溃
// 内存泄漏:忘记释放
char *leaked = (char *)malloc(1024);
// 没有free → 永久泄漏
}
2.3 性能问题
// JNI调用开销分析
public class JniOverhead {
// 每次JNI调用的隐性成本:
// 1. 线程状态转换(Java → Native):~10ns
// 2. 参数类型转换(jstring → char*):~20ns
// 3. 异常检查:~5ns
// 4. 阻止JIT内联优化
// 总计:单次调用 ~50-100ns
// 大数组传输需要复制
public native void processArray(byte[] data);
// C端:(*env)->GetByteArrayElements → 复制整个数组
// 处理完后:(*env)->ReleaseByteArrayElements → 再次复制
}
2.4 Panama的解决方案对比
| 胶水代码 | 需要C/C++ | 纯Java |
| 内存安全 | 无保护 | 边界检查+Arena |
| 调用开销 | ~50-100ns | ~5-10ns |
| 数据传输 | 需要复制 | 零拷贝 |
| 调试 | 跨语言困难 | 纯Java调试 |
| 工具支持 | 有限 | jextract自动生成 |
三、Foreign Function & Memory API
3.1 API核心架构
java.lang.foreign
├── MemorySegment → 内存段(数据载体)
├── Arena → 生命周期管理器
├── MemoryLayout → 内存布局描述
│ ├── ValueLayout → 基本类型布局
│ ├── StructLayout → 结构体布局
│ ├── UnionLayout → 联合体布局
│ ├── SequenceLayout→ 数组布局
│ └── PaddingLayout → 填充布局
├── Linker → 本地函数链接器
├── FunctionDescriptor→ 函数签名
├── SymbolLookup → 符号查找
└── ValueLayout → 值布局常量
├── JAVA_BYTE/SHORT/INT/LONG
├── JAVA_FLOAT/DOUBLE
├── JAVA_CHAR
└── ADDRESS
3.2 基本使用流程
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class PanamaHello {
public static void main(String[] args) throws Throwable {
// 1. 获取链接器
Linker linker = Linker.nativeLinker();
// 2. 查找C标准库函数
SymbolLookup lookup = linker.defaultLookup();
MemorySegment strlenAddr = lookup.find("strlen").orElseThrow();
// 3. 描述函数签名:size_t strlen(const char *s)
FunctionDescriptor desc = FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // 返回值:size_t
ValueLayout.ADDRESS // 参数:const char*
);
// 4. 创建MethodHandle
MethodHandle strlen = linker.downcallHandle(strlenAddr, desc);
// 5. 准备参数并调用
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateFrom("Hello, Panama!");
long length = (long) strlen.invokeExact(cString);
System.out.println("Length: " + length); // 14
}
}
}
3.3 编译与运行
# JDK 22+ 直接编译运行(无需额外参数)
javac PanamaHello.java
java PanamaHello
# JDK 19-21(Preview阶段需要)
javac –enable-preview –release 21 PanamaHello.java
java –enable-preview –enable-native-access=ALL-UNNAMED PanamaHello
四、MemorySegment(堆外内存管理)
4.1 基本概念
MemorySegment 是 Foreign Memory API 的核心,表示一段连续的、有边界的内存区域。它可以指向堆外内存(native memory)或包装Java数组。
import java.lang.foreign.*;
public class MemorySegmentDemo {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
// 分配100字节的堆外内存
MemorySegment segment = arena.allocate(100);
// 基本属性
System.out.println("地址: " + segment.address());
System.out.println("大小: " + segment.byteSize());
System.out.println("是否本地: " + segment.isNative());
}
// Arena关闭后,segment自动失效,再访问抛IllegalStateException
}
}
4.2 读写基本类型
public class SegmentReadWrite {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg = arena.allocate(64);
// 写入各种类型(指定偏移量)
seg.set(ValueLayout.JAVA_INT, 0, 42); // int at offset 0
seg.set(ValueLayout.JAVA_LONG, 8, 123456789L); // long at offset 8
seg.set(ValueLayout.JAVA_DOUBLE, 16, 3.14159); // double at offset 16
seg.set(ValueLayout.JAVA_BYTE, 24, (byte) 0xFF); // byte at offset 24
seg.set(ValueLayout.JAVA_CHAR, 26, 'A'); // char at offset 26
// 读取
int intVal = seg.get(ValueLayout.JAVA_INT, 0); // 42
long longVal = seg.get(ValueLayout.JAVA_LONG, 8); // 123456789
double dblVal = seg.get(ValueLayout.JAVA_DOUBLE, 16); // 3.14159
byte byteVal = seg.get(ValueLayout.JAVA_BYTE, 24); // -1 (0xFF)
char charVal = seg.get(ValueLayout.JAVA_CHAR, 26); // 'A'
System.out.printf("int=%d, long=%d, double=%.5f, byte=%d, char=%c%n",
intVal, longVal, dblVal, byteVal, charVal);
}
}
}
4.3 字符串操作
public class SegmentString {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
// 从Java字符串创建C字符串(自动添加'\\0')
MemorySegment cStr = arena.allocateFrom("Hello, World!");
System.out.println("C字符串大小: " + cStr.byteSize()); // 14 (含\\0)
// 读取C字符串
String javaStr = cStr.getString(0);
System.out.println("读回: " + javaStr); // Hello, World!
// 指定字符集
MemorySegment utf16Str = arena.allocateFrom(
java.nio.charset.StandardCharsets.UTF_16, "你好");
String chinese = utf16Str.getString(0, java.nio.charset.StandardCharsets.UTF_16);
System.out.println("中文: " + chinese); // 你好
}
}
}
4.4 数组操作
public class SegmentArray {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
// 分配int数组(10个元素)
MemorySegment intArray = arena.allocateArray(
ValueLayout.JAVA_INT, 10);
// 按索引写入
for (int i = 0; i < 10; i++) {
intArray.setAtIndex(ValueLayout.JAVA_INT, i, i * i);
}
// 按索引读取
for (int i = 0; i < 10; i++) {
int val = intArray.getAtIndex(ValueLayout.JAVA_INT, i);
System.out.printf("[%d] = %d%n", i, val);
}
// 转为Java数组
int[] javaArray = intArray.toArray(ValueLayout.JAVA_INT);
System.out.println("Java数组: " + java.util.Arrays.toString(javaArray));
// 从Java数组创建MemorySegment(零拷贝视图)
int[] source = {1, 2, 3, 4, 5};
MemorySegment wrapped = MemorySegment.ofArray(source);
System.out.println("包装大小: " + wrapped.byteSize()); // 20 bytes
}
}
}
4.5 切片与指针
public class SegmentSlice {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment buffer = arena.allocate(1024);
// 创建子切片
MemorySegment header = buffer.asSlice(0, 16); // 前16字节
MemorySegment body = buffer.asSlice(16, 256); // 16~271字节
header.set(ValueLayout.JAVA_INT, 0, 0xDEADBEEF);
body.set(ValueLayout.JAVA_INT, 0, 42);
// 指针操作:存储和读取地址
MemorySegment ptrSlot = arena.allocate(ValueLayout.ADDRESS);
ptrSlot.set(ValueLayout.ADDRESS, 0, buffer); // 存储buffer的地址
// 读取指针指向的段
MemorySegment dereferenced = ptrSlot.get(ValueLayout.ADDRESS, 0, 1024);
int magic = dereferenced.get(ValueLayout.JAVA_INT, 0);
System.out.printf("Magic: 0x%08X%n", magic); // 0xDEADBEEF
// NULL指针
MemorySegment nullPtr = MemorySegment.NULL;
System.out.println("NULL地址: " + nullPtr.address()); // 0
}
}
}
4.6 边界安全
public class SegmentSafety {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg = arena.allocate(8);
// 正常访问
seg.set(ValueLayout.JAVA_INT, 0, 42); // OK
// 越界访问 → 抛出 IndexOutOfBoundsException
try {
seg.set(ValueLayout.JAVA_INT, 8, 100); // offset 8 + 4 > 8
} catch (IndexOutOfBoundsException e) {
System.out.println("捕获越界: " + e.getMessage());
}
// Arena关闭后访问 → 抛出 IllegalStateException
}
// 此处seg已失效
// seg.get(ValueLayout.JAVA_INT, 0); // IllegalStateException!
}
}
五、Arena(生命周期管理)
5.1 Arena的作用
Arena 管理一组 MemorySegment 的生命周期。当 Arena 关闭时,其分配的所有内存段都会被释放,对这些段的后续访问将抛出异常。
5.2 Arena.ofConfined()(限定Arena)
public class ConfinedArenaDemo {
public static void main(String[] args) {
// 只能由创建线程访问,性能最优
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg = arena.allocate(1024);
seg.set(ValueLayout.JAVA_INT, 0, 42);
System.out.println(seg.get(ValueLayout.JAVA_INT, 0)); // 42
// 其他线程访问会抛出 WrongThreadException
// new Thread(() -> seg.get(ValueLayout.JAVA_INT, 0)).start(); // 错误!
}
// 关闭后内存释放
}
}
5.3 Arena.ofShared()(共享Arena)
import java.util.concurrent.CountDownLatch;
public class SharedArenaDemo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
try (Arena arena = Arena.ofShared()) {
MemorySegment shared = arena.allocate(ValueLayout.JAVA_INT);
shared.set(ValueLayout.JAVA_INT, 0, 0);
// 多线程安全访问
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 注意:非原子操作,仅演示可访问性
int val = shared.get(ValueLayout.JAVA_INT, 0);
shared.set(ValueLayout.JAVA_INT, 0, val + 1);
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("结果: " + shared.get(ValueLayout.JAVA_INT, 0));
}
}
}
5.4 Arena.ofAuto()(自动Arena)
public class AutoArenaDemo {
// 由GC管理,无需手动关闭
private static MemorySegment globalBuffer;
public static void init() {
Arena autoArena = Arena.ofAuto();
globalBuffer = autoArena.allocate(4096);
globalBuffer.set(ValueLayout.JAVA_INT, 0, 999);
// 不需要关闭arena,GC会处理
}
public static int getValue() {
// 只要globalBuffer可达,内存就不会被回收
return globalBuffer.get(ValueLayout.JAVA_INT, 0);
}
public static void main(String[] args) {
init();
System.out.println(getValue()); // 999
// 内存最终由GC回收(时机不确定)
}
}
5.5 Arena.global()(全局Arena)
public class GlobalArenaDemo {
public static void main(String[] args) {
// 用于包装已存在的全局内存(如C全局变量)
// 永不释放
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
// 例如访问C标准库的全局变量(如 stdout)
// 实际使用中较少直接操作global arena
Arena global = Arena.global();
System.out.println("Global arena: " + global);
}
}
5.6 Arena选择指南
┌────────────────────────────────────────────────────────────┐
│ Arena 选择决策树 │
├────────────────────────────────────────────────────────────┤
│ │
│ 需要多线程访问? │
│ ├── 否 → 需要确定性释放? │
│ │ ├── 是 → Arena.ofConfined()(推荐) │
│ │ └── 否 → Arena.ofAuto() │
│ └── 是 → 需要确定性释放? │
│ ├── 是 → Arena.ofShared() │
│ └── 否 → Arena.ofAuto() │
│ │
│ 包装已存在的全局内存?→ Arena.global() │
└────────────────────────────────────────────────────────────┘
六、MemoryLayout(结构体布局)
6.1 基本布局类型
import java.lang.foreign.*;
public class LayoutBasics {
public static void main(String[] args) {
// ValueLayout:基本类型
ValueLayout intLayout = ValueLayout.JAVA_INT; // 4 bytes
ValueLayout longLayout = ValueLayout.JAVA_LONG; // 8 bytes
ValueLayout doubleLayout = ValueLayout.JAVA_DOUBLE; // 8 bytes
ValueLayout addrLayout = ValueLayout.ADDRESS; // 平台相关(4或8)
System.out.println("int大小: " + intLayout.byteSize());
System.out.println("long大小: " + longLayout.byteSize());
System.out.println("指针大小: " + addrLayout.byteSize());
// 字节序
ValueLayout bigEndianInt = ValueLayout.JAVA_INT.withOrder(
java.nio.ByteOrder.BIG_ENDIAN);
ValueLayout littleEndianInt = ValueLayout.JAVA_INT.withOrder(
java.nio.ByteOrder.LITTLE_ENDIAN);
}
}
6.2 结构体布局(StructLayout)
public class StructLayoutDemo {
// 对应C结构体:
// struct Point {
// int x; // offset 0, size 4
// int y; // offset 4, size 4
// double z; // offset 8, size 8(对齐到8字节)
// };
static final StructLayout POINT_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"),
ValueLayout.JAVA_DOUBLE.withName("z")
);
public static void main(String[] args) {
System.out.println("Point大小: " + POINT_LAYOUT.byteSize()); // 16
System.out.println("Point对齐: " + POINT_LAYOUT.byteAlignment()); // 8
try (Arena arena = Arena.ofConfined()) {
MemorySegment point = arena.allocate(POINT_LAYOUT);
// 通过偏移量访问
long xOffset = POINT_LAYOUT.byteOffset(
MemoryLayout.PathElement.groupElement("x"));
long yOffset = POINT_LAYOUT.byteOffset(
MemoryLayout.PathElement.groupElement("y"));
long zOffset = POINT_LAYOUT.byteOffset(
MemoryLayout.PathElement.groupElement("z"));
point.set(ValueLayout.JAVA_INT, xOffset, 10);
point.set(ValueLayout.JAVA_INT, yOffset, 20);
point.set(ValueLayout.JAVA_DOUBLE, zOffset, 3.5);
System.out.printf("Point(%d, %d, %.1f)%n",
point.get(ValueLayout.JAVA_INT, xOffset),
point.get(ValueLayout.JAVA_INT, yOffset),
point.get(ValueLayout.JAVA_DOUBLE, zOffset));
}
}
}
6.3 使用VarHandle高效访问
import java.lang.invoke.VarHandle;
public class VarHandleLayout {
static final StructLayout RECT_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"),
ValueLayout.JAVA_INT.withName("width"),
ValueLayout.JAVA_INT.withName("height")
);
// 创建VarHandle(一次创建,重复使用)
static final VarHandle X_HANDLE = RECT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("x"));
static final VarHandle Y_HANDLE = RECT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("y"));
static final VarHandle WIDTH_HANDLE = RECT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("width"));
static final VarHandle HEIGHT_HANDLE = RECT_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("height"));
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment rect = arena.allocate(RECT_LAYOUT);
// 使用VarHandle读写(比每次计算offset更高效)
X_HANDLE.set(rect, 0L, 100);
Y_HANDLE.set(rect, 0L, 200);
WIDTH_HANDLE.set(rect, 0L, 800);
HEIGHT_HANDLE.set(rect, 0L, 600);
int x = (int) X_HANDLE.get(rect, 0L);
int w = (int) WIDTH_HANDLE.get(rect, 0L);
System.out.printf("Rect: x=%d, width=%d%n", x, w);
}
}
}
6.4 数组布局(SequenceLayout)
public class SequenceLayoutDemo {
// 对应C:int matrix[3][4];
static final SequenceLayout MATRIX_LAYOUT = MemoryLayout.sequenceLayout(3,
MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_INT));
public static void main(String[] args) {
System.out.println("矩阵大小: " + MATRIX_LAYOUT.byteSize()); // 48 bytes
// 二维数组的VarHandle
VarHandle elementHandle = MATRIX_LAYOUT.varHandle(
MemoryLayout.PathElement.sequenceElement(), // 行(动态索引)
MemoryLayout.PathElement.sequenceElement() // 列(动态索引)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment matrix = arena.allocate(MATRIX_LAYOUT);
// 填充矩阵
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
elementHandle.set(matrix, 0L, (long) i, (long) j, i * 4 + j);
}
}
// 读取并打印
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
int val = (int) elementHandle.get(matrix, 0L, (long) i, (long) j);
System.out.printf("%3d", val);
}
System.out.println();
}
}
}
}
6.5 嵌套结构体与联合体
public class NestedLayoutDemo {
// struct Address { char street[64]; int zip; };
static final StructLayout ADDRESS_LAYOUT = MemoryLayout.structLayout(
MemoryLayout.sequenceLayout(64, ValueLayout.JAVA_BYTE).withName("street"),
ValueLayout.JAVA_INT.withName("zip")
);
// struct Person { char name[32]; int age; struct Address addr; };
static final StructLayout PERSON_LAYOUT = MemoryLayout.structLayout(
MemoryLayout.sequenceLayout(32, ValueLayout.JAVA_BYTE).withName("name"),
ValueLayout.JAVA_INT.withName("age"),
ADDRESS_LAYOUT.withName("address")
);
// union Variant { int i; double d; void* ptr; };
static final UnionLayout VARIANT_LAYOUT = MemoryLayout.unionLayout(
ValueLayout.JAVA_INT.withName("intValue"),
ValueLayout.JAVA_DOUBLE.withName("doubleValue"),
ValueLayout.ADDRESS.withName("ptrValue")
);
public static void main(String[] args) {
System.out.println("Person大小: " + PERSON_LAYOUT.byteSize());
System.out.println("Variant大小: " + VARIANT_LAYOUT.byteSize()); // 8(最大成员)
// 访问嵌套成员
VarHandle zipHandle = PERSON_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("address"),
MemoryLayout.PathElement.groupElement("zip")
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment person = arena.allocate(PERSON_LAYOUT);
zipHandle.set(person, 0L, 100000);
int zip = (int) zipHandle.get(person, 0L);
System.out.println("邮编: " + zip);
}
}
}
七、Linker(本地函数链接)
7.1 获取Linker实例
public class LinkerDemo {
public static void main(String[] args) {
// 获取平台原生链接器(单例)
Linker linker = Linker.nativeLinker();
// 获取默认符号查找(C标准库)
SymbolLookup defaultLookup = linker.defaultLookup();
// 查找函数地址
MemorySegment printfAddr = defaultLookup.find("printf").orElseThrow();
System.out.println("printf地址: 0x" + Long.toHexString(printfAddr.address()));
MemorySegment mallocAddr = defaultLookup.find("malloc").orElseThrow();
System.out.println("malloc地址: 0x" + Long.toHexString(mallocAddr.address()));
}
}
7.2 下行调用(Downcall)
public class DowncallDemo {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
// 创建 abs 函数的 downcall handle
// int abs(int n);
MemorySegment absAddr = lookup.find("abs").orElseThrow();
FunctionDescriptor absDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT, // 返回值
ValueLayout.JAVA_INT // 参数
);
MethodHandle abs = linker.downcallHandle(absAddr, absDesc);
// 调用
int result = (int) abs.invokeExact(–42);
System.out.println("abs(-42) = " + result); // 42
// 创建 pow 函数的 downcall handle
// double pow(double base, double exp);
MemorySegment powAddr = lookup.find("pow").orElseThrow();
FunctionDescriptor powDesc = FunctionDescriptor.of(
ValueLayout.JAVA_DOUBLE,
ValueLayout.JAVA_DOUBLE,
ValueLayout.JAVA_DOUBLE
);
MethodHandle pow = linker.downcallHandle(powAddr, powDesc);
double power = (double) pow.invokeExact(2.0, 10.0);
System.out.println("pow(2, 10) = " + power); // 1024.0
}
}
7.3 上行调用(Upcall)
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class UpcallDemo {
// Java回调方法(将被C代码调用)
static int compareInts(MemorySegment a, MemorySegment b) {
int va = a.get(ValueLayout.JAVA_INT, 0);
int vb = b.get(ValueLayout.JAVA_INT, 0);
return Integer.compare(va, vb);
}
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
// 创建upcall stub(将Java方法包装为C函数指针)
MethodHandle compareHandle = MethodHandles.lookup().findStatic(
UpcallDemo.class,
"compareInts",
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
);
// 描述C回调签名:int (*)(const void*, const void*)
FunctionDescriptor compareDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
try (Arena arena = Arena.ofConfined()) {
// 创建函数指针
MemorySegment callbackPtr = linker.upcallStub(
compareHandle, compareDesc, arena);
System.out.println("回调指针: 0x" + Long.toHexString(callbackPtr.address()));
// 可以将callbackPtr传给C函数(如qsort)
}
// Arena关闭后,callbackPtr失效
}
}
7.4 加载自定义动态库
import java.nio.file.Path;
public class LibraryLoadDemo {
public static void main(String[] args) throws Throwable {
// 方式1:通过路径加载
Path libPath = Path.of("/usr/lib/libm.so.6"); // Linux
// Path libPath = Path.of("C:\\\\Windows\\\\System32\\\\msvcrt.dll"); // Windows
try (Arena arena = Arena.ofConfined()) {
SymbolLookup libLookup = SymbolLookup.libraryLookup(libPath, arena);
// 查找库中的符号
MemorySegment sinAddr = libLookup.find("sin").orElseThrow();
System.out.println("sin地址: 0x" + Long.toHexString(sinAddr.address()));
}
// 方式2:通过库名加载(使用系统搜索路径)
try (Arena arena = Arena.ofConfined()) {
SymbolLookup libLookup = SymbolLookup.libraryLookup("m", arena);
MemorySegment cosAddr = libLookup.find("cos").orElseThrow();
Linker linker = Linker.nativeLinker();
FunctionDescriptor cosDesc = FunctionDescriptor.of(
ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE);
MethodHandle cos = linker.downcallHandle(cosAddr, cosDesc);
double result = (double) cos.invokeExact(0.0);
System.out.println("cos(0) = " + result); // 1.0
}
}
}
八、FunctionDescriptor(函数签名描述)
8.1 创建函数描述符
public class DescriptorDemo {
public static void main(String[] args) {
// 有返回值的函数:int add(int, int)
FunctionDescriptor addDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT, // 返回类型
ValueLayout.JAVA_INT, // 参数1
ValueLayout.JAVA_INT // 参数2
);
// void函数:void printf(const char*, …)
FunctionDescriptor printfDesc = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS // const char*
// 可变参数不在descriptor中描述
);
// 指针参数:void* malloc(size_t)
FunctionDescriptor mallocDesc = FunctionDescriptor.of(
ValueLayout.ADDRESS, // 返回 void*
ValueLayout.JAVA_LONG // size_t(64位平台)
);
// 无参函数:int rand(void)
FunctionDescriptor randDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT // 仅返回类型
);
// 查询描述符信息
System.out.println("返回类型: " + addDesc.returnLayout());
System.out.println("参数列表: " + addDesc.argumentLayouts());
}
}
8.2 C类型到Java布局映射
public class TypeMapping {
/*
* C类型 Java ValueLayout 大小(64位Linux)
* ─────────────────────────────────────────────────────────────
* char JAVA_BYTE 1
* short JAVA_SHORT 2
* int JAVA_INT 4
* long JAVA_LONG 8 (Linux) / 4 (Windows)
* long long JAVA_LONG 8
* float JAVA_FLOAT 4
* double JAVA_DOUBLE 8
* char* ADDRESS 8
* void* ADDRESS 8
* size_t JAVA_LONG 8 (64位)
* int8_t JAVA_BYTE 1
* int16_t JAVA_SHORT 2
* int32_t JAVA_INT 4
* int64_t JAVA_LONG 8
* uint8_t JAVA_BYTE (unsigned) 1
* uint16_t JAVA_SHORT (unsigned) 2
* uint32_t JAVA_INT (unsigned) 4
* uint64_t JAVA_LONG (unsigned) 8
*/
// 注意:C的long在Windows 64位上是4字节!
// 跨平台时需要特别处理
static ValueLayout cLong() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return ValueLayout.JAVA_INT; // Windows: long = 4 bytes
}
return ValueLayout.JAVA_LONG; // Linux/Mac: long = 8 bytes
}
}
8.3 结构体作为参数/返回值
public class StructDescriptor {
// struct Point { int x; int y; };
// struct Point make_point(int x, int y);
// double distance(struct Point p1, struct Point p2);
static final StructLayout POINT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")
);
public static void main(String[] args) {
// 返回结构体的函数
FunctionDescriptor makePointDesc = FunctionDescriptor.of(
POINT, // 返回 struct Point
ValueLayout.JAVA_INT, // int x
ValueLayout.JAVA_INT // int y
);
// 结构体按值传递(注意:大结构体可能按引用传递,取决于ABI)
FunctionDescriptor distanceDesc = FunctionDescriptor.of(
ValueLayout.JAVA_DOUBLE, // 返回 double
POINT, // struct Point p1(按值)
POINT // struct Point p2(按值)
);
// 结构体指针传递(更常见)
// double distance_ptr(const struct Point* p1, const struct Point* p2);
FunctionDescriptor distancePtrDesc = FunctionDescriptor.of(
ValueLayout.JAVA_DOUBLE,
ValueLayout.ADDRESS, // const struct Point*
ValueLayout.ADDRESS // const struct Point*
);
}
}
九、MethodHandle调用本地函数
9.1 invoke与invokeExact
public class MethodHandleInvoke {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
MemorySegment absAddr = lookup.find("abs").orElseThrow();
FunctionDescriptor desc = FunctionDescriptor.of(
ValueLayout.JAVA_INT, ValueLayout.JAVA_INT);
MethodHandle abs = linker.downcallHandle(absAddr, desc);
// invokeExact:严格类型匹配,不做自动转换
int r1 = (int) abs.invokeExact(–5); // 必须精确匹配int
// invoke:允许自动类型转换(装箱/拆箱/拓宽)
Object r2 = abs.invoke(–5); // 返回Object,内部做转换
System.out.println("invokeExact: " + r1);
System.out.println("invoke: " + r2);
// 性能提示:invokeExact更快(无适配开销)
// 推荐在性能敏感路径使用invokeExact
}
}
9.2 缓存MethodHandle
public class CachedHandles {
private static final Linker LINKER = Linker.nativeLinker();
private static final SymbolLookup LOOKUP = LINKER.defaultLookup();
// 缓存MethodHandle(创建成本高,应复用)
private static final MethodHandle STRLEN;
private static final MethodHandle PUTS;
private static final MethodHandle MALLOC;
private static final MethodHandle FREE;
static {
try {
STRLEN = LINKER.downcallHandle(
LOOKUP.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
PUTS = LINKER.downcallHandle(
LOOKUP.find("puts").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
);
MALLOC = LINKER.downcallHandle(
LOOKUP.find("malloc").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
);
FREE = LINKER.downcallHandle(
LOOKUP.find("free").orElseThrow(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static long strlen(String s) throws Throwable {
try (Arena arena = Arena.ofConfined()) {
MemorySegment cStr = arena.allocateFrom(s);
return (long) STRLEN.invokeExact(cStr);
}
}
public static void main(String[] args) throws Throwable {
System.out.println(strlen("Hello")); // 5
PUTS.invokeExact(
Arena.ofAuto().allocateFrom("Hello from C puts!"));
}
}
9.3 可变参数函数调用
public class VariadicCall {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
// printf 是可变参数函数
// 对于可变参数,需要为每次调用指定具体签名
MemorySegment printfAddr = lookup.find("printf").orElseThrow();
// 调用 printf("Hello %s, you are %d\\n", name, age)
// 需要描述实际参数类型
FunctionDescriptor printfDesc = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS, // format string
ValueLayout.ADDRESS, // %s → char*
ValueLayout.JAVA_INT // %d → int
);
// 使用 Linker.Option.firstVariadicArg(1) 标记可变参数起始位置
MethodHandle printf = linker.downcallHandle(
printfAddr,
printfDesc,
Linker.Option.firstVariadicArg(1) // 第1个参数开始是可变参数
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment fmt = arena.allocateFrom("Hello %s, age %d!%n");
MemorySegment name = arena.allocateFrom("Panama");
printf.invokeExact(fmt, name, 22);
// 输出: Hello Panama, age 22!
}
}
}
十、调用C标准库实战
10.1 strlen – 字符串长度
public class StrlenExample {
private static final Linker LINKER = Linker.nativeLinker();
private static final MethodHandle STRLEN;
static {
try {
STRLEN = LINKER.downcallHandle(
LINKER.defaultLookup().find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static long strlen(String s) throws Throwable {
try (Arena arena = Arena.ofConfined()) {
return (long) STRLEN.invokeExact(arena.allocateFrom(s));
}
}
public static void main(String[] args) throws Throwable {
System.out.println(strlen("Hello, World!")); // 13
System.out.println(strlen("Project Panama")); // 14
System.out.println(strlen("")); // 0
System.out.println(strlen("中文测试")); // 12 (UTF-8)
}
}
10.2 qsort – 快速排序
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class QsortExample {
private static final Linker LINKER = Linker.nativeLinker();
private static final SymbolLookup LOOKUP = LINKER.defaultLookup();
// 比较器:int compare(const void* a, const void* b)
static int intCompare(MemorySegment a, MemorySegment b) {
int va = a.get(ValueLayout.JAVA_INT, 0);
int vb = b.get(ValueLayout.JAVA_INT, 0);
return Integer.compare(va, vb);
}
public static void main(String[] args) throws Throwable {
// void qsort(void* base, size_t nmemb, size_t size,
// int (*compar)(const void*, const void*));
MethodHandle qsort = LINKER.downcallHandle(
LOOKUP.find("qsort").orElseThrow(),
FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS, // base
ValueLayout.JAVA_LONG, // nmemb
ValueLayout.JAVA_LONG, // size
ValueLayout.ADDRESS // compar
)
);
// 创建比较器回调
MethodHandle compareHandle = MethodHandles.lookup().findStatic(
QsortExample.class, "intCompare",
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
);
try (Arena arena = Arena.ofConfined()) {
// 准备数据
int[] data = {64, 25, 12, 22, 11, 90, 1, 99, 45};
MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT, data);
// 创建upcall stub
MemorySegment comparator = LINKER.upcallStub(
compareHandle,
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.ADDRESS),
arena
);
// 调用qsort
qsort.invokeExact(
array,
(long) data.length,
(long) ValueLayout.JAVA_INT.byteSize(),
comparator
);
// 读取排序结果
int[] sorted = array.toArray(ValueLayout.JAVA_INT);
System.out.println("排序后: " + java.util.Arrays.toString(sorted));
// [1, 11, 12, 22, 25, 45, 64, 99, 20]
}
}
}
10.3 memcpy与memset
public class MemOpsExample {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
// void* memcpy(void* dest, const void* src, size_t n);
MethodHandle memcpy = linker.downcallHandle(
lookup.find("memcpy").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS,
ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
);
// void* memset(void* s, int c, size_t n);
MethodHandle memset = linker.downcallHandle(
lookup.find("memset").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS,
ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment src = arena.allocateFrom("Hello, Panama!");
MemorySegment dest = arena.allocate(64);
// memset清零
memset.invokeExact(dest, 0, 64L);
// memcpy复制
memcpy.invokeExact(dest, src, src.byteSize());
System.out.println("复制结果: " + dest.getString(0));
// Hello, Panama!
}
}
}
10.4 调用数学库
public class MathLibExample {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
// double sqrt(double x)
MethodHandle sqrt = linker.downcallHandle(
lookup.find("sqrt").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
// double sin(double x)
MethodHandle sin = linker.downcallHandle(
lookup.find("sin").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
// double atan2(double y, double x)
MethodHandle atan2 = linker.downcallHandle(
lookup.find("atan2").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
double sqrtResult = (double) sqrt.invokeExact(144.0);
double sinResult = (double) sin.invokeExact(Math.PI / 2);
double atanResult = (double) atan2.invokeExact(1.0, 1.0);
System.out.printf("sqrt(144) = %.1f%n", sqrtResult); // 12.0
System.out.printf("sin(PI/2) = %.1f%n", sinResult); // 1.0
System.out.printf("atan2(1,1) = %.6f%n", atanResult); // 0.785398
}
}
十一、调用自定义本地库
11.1 编写C源文件
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
double real;
double imag;
} Complex;
Complex complex_add(Complex a, Complex b);
Complex complex_multiply(Complex a, Complex b);
double complex_magnitude(Complex c);
// 数组操作
void array_scale(double* arr, int len, double factor);
double array_dot(const double* a, const double* b, int len);
// 回调
typedef void (*callback_t)(int progress, void* user_data);
void long_running_task(int iterations, callback_t cb, void* user_data);
#endif
// mylib.c
#include "mylib.h"
#include <math.h>
Complex complex_add(Complex a, Complex b) {
Complex result = {a.real + b.real, a.imag + b.imag};
return result;
}
Complex complex_multiply(Complex a, Complex b) {
Complex result = {
a.real * b.real – a.imag * b.imag,
a.real * b.imag + a.imag * b.real
};
return result;
}
double complex_magnitude(Complex c) {
return sqrt(c.real * c.real + c.imag * c.imag);
}
void array_scale(double* arr, int len, double factor) {
for (int i = 0; i < len; i++) {
arr[i] *= factor;
}
}
double array_dot(const double* a, const double* b, int len) {
double sum = 0.0;
for (int i = 0; i < len; i++) {
sum += a[i] * b[i];
}
return sum;
}
void long_running_task(int iterations, callback_t cb, void* user_data) {
for (int i = 0; i < iterations; i++) {
// 模拟工作…
if (cb && (i % (iterations / 10) == 0)) {
cb(i * 100 / iterations, user_data);
}
}
}
11.2 编译动态库
# Linux
gcc -shared -fPIC -o libmylib.so mylib.c -lm
# macOS
gcc -shared -fPIC -o libmylib.dylib mylib.c -lm
# Windows (MSVC)
cl /LD mylib.c /Fe:mylib.dll
# Windows (MinGW)
gcc -shared -o mylib.dll mylib.c -lm
11.3 Java端调用
import java.nio.file.Path;
public class CustomLibDemo {
// Complex结构体布局
static final StructLayout COMPLEX_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_DOUBLE.withName("real"),
ValueLayout.JAVA_DOUBLE.withName("imag")
);
static final VarHandle REAL_HANDLE = COMPLEX_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("real"));
static final VarHandle IMAG_HANDLE = COMPLEX_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("imag"));
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
// 加载自定义库
Path libPath = Path.of("./libmylib.so"); // 根据平台调整
try (Arena arena = Arena.ofConfined()) {
SymbolLookup lib = SymbolLookup.libraryLookup(libPath, arena);
// complex_add
MethodHandle complexAdd = linker.downcallHandle(
lib.find("complex_add").orElseThrow(),
FunctionDescriptor.of(COMPLEX_LAYOUT, COMPLEX_LAYOUT, COMPLEX_LAYOUT)
);
// 准备参数
MemorySegment a = arena.allocate(COMPLEX_LAYOUT);
REAL_HANDLE.set(a, 0L, 3.0);
IMAG_HANDLE.set(a, 0L, 4.0);
MemorySegment b = arena.allocate(COMPLEX_LAYOUT);
REAL_HANDLE.set(b, 0L, 1.0);
IMAG_HANDLE.set(b, 0L, 2.0);
// 调用
MemorySegment result = (MemorySegment) complexAdd.invokeExact(a, b);
double real = (double) REAL_HANDLE.get(result, 0L);
double imag = (double) IMAG_HANDLE.get(result, 0L);
System.out.printf("(3+4i) + (1+2i) = %.1f+%.1fi%n", real, imag);
// (3+4i) + (1+2i) = 4.0+6.0i
// array_dot
MethodHandle arrayDot = linker.downcallHandle(
lib.find("array_dot").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
);
MemorySegment vecA = arena.allocateArray(ValueLayout.JAVA_DOUBLE,
new double[]{1.0, 2.0, 3.0});
MemorySegment vecB = arena.allocateArray(ValueLayout.JAVA_DOUBLE,
new double[]{4.0, 5.0, 6.0});
double dot = (double) arrayDot.invokeExact(vecA, vecB, 3);
System.out.println("点积: " + dot); // 32.0
}
}
}
十二、回调(Upcall Stub)
12.1 基本概念
Upcall Stub 将一个 Java MethodHandle 包装为一个本地函数指针(MemorySegment),使得C代码可以回调Java方法。
┌──────────────┐ downcall ┌──────────────┐
│ Java Code │ ────────────────→ │ C Function │
│ │ ←──────────────── │ │
└──────────────┘ upcall └──────────────┘
(callback)
12.2 进度回调示例
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class CallbackDemo {
// 回调方法:void on_progress(int progress, void* user_data)
static void onProgress(int progress, MemorySegment userData) {
System.out.println("进度: " + progress + "%");
}
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
try (Arena arena = Arena.ofConfined()) {
SymbolLookup lib = SymbolLookup.libraryLookup(
Path.of("./libmylib.so"), arena);
// void long_running_task(int iterations, callback_t cb, void* user_data)
MethodHandle task = linker.downcallHandle(
lib.find("long_running_task").orElseThrow(),
FunctionDescriptor.ofVoid(
ValueLayout.JAVA_INT, // iterations
ValueLayout.ADDRESS, // callback
ValueLayout.ADDRESS // user_data
)
);
// 创建回调stub
MethodHandle progressHandle = MethodHandles.lookup().findStatic(
CallbackDemo.class, "onProgress",
MethodType.methodType(void.class, int.class, MemorySegment.class)
);
MemorySegment callbackStub = linker.upcallStub(
progressHandle,
FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, ValueLayout.ADDRESS),
arena // 生命周期绑定
);
// 调用(C代码会回调Java)
task.invokeExact(1000000, callbackStub, MemorySegment.NULL);
}
}
}
12.3 排序比较器回调
public class SortCallback {
// 降序比较器
static int descendingCompare(MemorySegment a, MemorySegment b) {
double va = a.get(ValueLayout.JAVA_DOUBLE, 0);
double vb = b.get(ValueLayout.JAVA_DOUBLE, 0);
return Double.compare(vb, va); // 降序
}
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
MethodHandle qsort = linker.downcallHandle(
lookup.find("qsort").orElseThrow(),
FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS, ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
MethodHandle compHandle = MethodHandles.lookup().findStatic(
SortCallback.class, "descendingCompare",
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)
);
try (Arena arena = Arena.ofConfined()) {
double[] data = {3.14, 1.41, 2.72, 0.57, 1.73};
MemorySegment arr = arena.allocateArray(ValueLayout.JAVA_DOUBLE, data);
MemorySegment comp = linker.upcallStub(
compHandle,
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.ADDRESS),
arena
);
qsort.invokeExact(arr, (long) data.length,
ValueLayout.JAVA_DOUBLE.byteSize(), comp);
double[] sorted = arr.toArray(ValueLayout.JAVA_DOUBLE);
System.out.println("降序: " + java.util.Arrays.toString(sorted));
// [3.14, 2.72, 1.73, 1.41, 0.57]
}
}
}
12.4 Upcall注意事项
public class UpcallNotes {
/*
* 重要注意事项:
*
* 1. 生命周期:Upcall stub绑定到Arena,Arena关闭后stub失效
* – 不要将stub存储在比Arena更长生命周期的地方
*
* 2. 线程安全:
* – C代码可能从任意线程调用upcall
* – 回调方法必须是线程安全的
* – 使用Arena.ofShared()如果回调中需要访问共享内存
*
* 3. 异常处理:
* – 回调方法中不要抛出受检异常
* – 未捕获异常会导致未定义行为
* – 建议在回调中try-catch所有异常
*
* 4. 性能:
* – Upcall比downcall开销更大
* – 避免在紧密循环中频繁upcall
* – 考虑批量处理减少回调次数
*
* 5. 重入:
* – C代码可能在upcall中再次downcall到Java
* – 注意避免死锁
*/
// 安全的回调实现示例
static void safeCallback(int code, MemorySegment data) {
try {
// 业务逻辑
System.out.println("Callback code: " + code);
} catch (Exception e) {
// 绝不让异常逃逸到C代码
e.printStackTrace();
}
}
}
十三、与JNI性能对比
13.1 基准测试代码
public class PanamaVsJniBenchmark {
// 测试1:简单函数调用开销
// JNI: native int nativeAdd(int a, int b);
// Panama: int add(int a, int b) via downcall
private static final Linker LINKER = Linker.nativeLinker();
private static final MethodHandle C_ABS;
static {
try {
C_ABS = LINKER.downcallHandle(
LINKER.defaultLookup().find("abs").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
// Panama方式
public static int panamaAbs(int x) throws Throwable {
return (int) C_ABS.invokeExact(x);
}
// JNI方式(对比)
// public static native int jniAbs(int x);
public static void main(String[] args) throws Throwable {
int iterations = 10_000_000;
// 预热
for (int i = 0; i < 100_000; i++) {
panamaAbs(–i);
}
// Panama基准
long start = System.nanoTime();
int sum = 0;
for (int i = 0; i < iterations; i++) {
sum += panamaAbs(–i);
}
long panamaTime = System.nanoTime() – start;
System.out.printf("Panama: %d ms (%.1f ns/call)%n",
panamaTime / 1_000_000,
(double) panamaTime / iterations);
// 典型结果:~5-10 ns/call
// JNI典型结果:~50-100 ns/call
// 纯Java Math.abs:~0.3 ns/call(JIT内联后)
}
}
13.2 内存操作性能对比
public class MemoryPerfCompare {
public static void main(String[] args) {
int size = 1_000_000; // 100万个int
// Panama:直接操作堆外内存(零拷贝)
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocateArray(ValueLayout.JAVA_INT, size);
long start = System.nanoTime();
for (int i = 0; i < size; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
}
long writeTime = System.nanoTime() – start;
start = System.nanoTime();
int sum = 0;
for (int i = 0; i < size; i++) {
sum += segment.getAtIndex(ValueLayout.JAVA_INT, i);
}
long readTime = System.nanoTime() – start;
System.out.printf("Panama写入: %.2f ms%n", writeTime / 1e6);
System.out.printf("Panama读取: %.2f ms%n", readTime / 1e6);
}
// JNI方式(对比说明):
// 1. GetIntArrayElements → 可能复制整个数组 (~数ms)
// 2. 操作本地副本
// 3. ReleaseIntArrayElements → 可能再次复制 (~数ms)
// 总开销远大于Panama的直接访问
}
}
13.3 性能对比总结
┌─────────────────────────────────────────────────────────────┐
│ Panama vs JNI 性能对比 │
├─────────────────────┬──────────────┬────────────────────────┤
│ 操作 │ JNI │ Panama │
├─────────────────────┼──────────────┼────────────────────────┤
│ 简单函数调用 │ ~50-100 ns │ ~5-10 ns │
│ 带字符串参数调用 │ ~200-500 ns │ ~50-100 ns │
│ 大数组传输(1MB) │ ~1-5 ms │ ~0 (零拷贝) │
│ 内存分配/释放 │ ~100 ns │ ~10 ns (Arena批量) │
│ 回调调用 │ ~100-200 ns │ ~20-50 ns │
├─────────────────────┼──────────────┼────────────────────────┤
│ GC压力 │ 高(临时对象)│ 低(Arena管理) │
│ JIT优化 │ 阻止内联 │ 可优化 │
│ 内存泄漏风险 │ 高 │ 极低 │
└─────────────────────┴──────────────┴────────────────────────┘
十四、jextract工具
14.1 概述
jextract 是一个独立工具,能从C头文件自动生成Java绑定代码,大幅减少手工编写绑定的工作量。
14.2 安装与使用
# 下载 jextract(从 https://jdk.java.net/jextract/)
# 解压后使用
# 基本用法:从C头文件生成Java代码
jextract –source \\
-t com.example.native \\
-l m \\
–header-file-name math.h \\
/usr/include/math.h
# 参数说明:
# –source → 生成Java源码(而非class文件)
# -t <package> → 目标Java包名
# -l <lib> → 链接的库名
# –header-file-name → 头文件名(用于生成类名)
# 最后一个参数 → 头文件路径
14.3 生成代码示例
# 假设有头文件 mylib.h
jextract –source -t com.example.mylib -l mylib mylib.h
// 自动生成的代码(简化示意):com/example/mylib/mylib_h.java
package com.example.mylib;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
public class mylib_h {
// 库加载
static final SymbolLookup LIBRARY = SymbolLookup.libraryLookup("mylib", Arena.ofAuto());
static final Linker LINKER = Linker.nativeLinker();
// struct Complex 布局
public static final StructLayout Complex$LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_DOUBLE.withName("real"),
ValueLayout.JAVA_DOUBLE.withName("imag")
);
public static final VarHandle Complex$real$VH =
Complex$LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("real"));
public static final VarHandle Complex$imag$VH =
Complex$LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("imag"));
// complex_add 函数
static final MethodHandle complex_add$MH = LINKER.downcallHandle(
LIBRARY.find("complex_add").orElseThrow(),
FunctionDescriptor.of(Complex$LAYOUT, Complex$LAYOUT, Complex$LAYOUT)
);
public static MemorySegment complex_add(MemorySegment a, MemorySegment b) {
try {
return (MemorySegment) complex_add$MH.invokeExact(a, b);
} catch (Throwable e) {
throw new AssertionError(e);
}
}
// array_dot 函数
static final MethodHandle array_dot$MH = LINKER.downcallHandle(
LIBRARY.find("array_dot").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
);
public static double array_dot(MemorySegment a, MemorySegment b, int len) {
try {
return (double) array_dot$MH.invokeExact(a, b, len);
} catch (Throwable e) {
throw new AssertionError(e);
}
}
}
14.4 使用生成的代码
import com.example.mylib.mylib_h;
public class UseGenerated {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
// 使用生成的布局
MemorySegment a = arena.allocate(mylib_h.Complex$LAYOUT);
mylib_h.Complex$real$VH.set(a, 0L, 3.0);
mylib_h.Complex$imag$VH.set(a, 0L, 4.0);
MemorySegment b = arena.allocate(mylib_h.Complex$LAYOUT);
mylib_h.Complex$real$VH.set(b, 0L, 1.0);
mylib_h.Complex$imag$VH.set(b, 0L, 2.0);
// 调用生成的函数包装
MemorySegment result = mylib_h.complex_add(a, b);
double real = (double) mylib_h.Complex$real$VH.get(result, 0L);
double imag = (double) mylib_h.Complex$imag$VH.get(result, 0L);
System.out.printf("结果: %.1f + %.1fi%n", real, imag);
}
}
}
14.5 jextract配置选项
# 过滤:只生成特定函数/结构体
jextract –source \\
-t com.example.lib \\
–include-function strlen \\
–include-function qsort \\
–include-struct timeval \\
–include-macro NULL \\
-I /usr/include \\
/usr/include/string.h
# 处理系统头文件依赖
jextract –source \\
-t com.example.lib \\
-I /usr/include \\
-I /usr/include/x86_64-linux-gnu \\
-D __STDC_VERSION__=201112L \\
mylib.h
十五、最佳实践
15.1 封装为Java友好API
/**
* 将Panama底层调用封装为面向对象的Java API
*/
public class NativeMath implements AutoCloseable {
private final Arena arena;
private final SymbolLookup lib;
private final Linker linker;
// 缓存所有MethodHandle
private final MethodHandle sqrtHandle;
private final MethodHandle powHandle;
public NativeMath(String libPath) {
this.arena = Arena.ofShared();
this.lib = SymbolLookup.libraryLookup(Path.of(libPath), arena);
this.linker = Linker.nativeLinker();
try {
this.sqrtHandle = linker.downcallHandle(
lib.find("sqrt").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
this.powHandle = linker.downcallHandle(
lib.find("pow").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE,
ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
} catch (Exception e) {
arena.close();
throw new RuntimeException("Failed to load native library", e);
}
}
public double sqrt(double x) {
try {
return (double) sqrtHandle.invokeExact(x);
} catch (Throwable e) {
throw new RuntimeException("Native call failed", e);
}
}
public double pow(double base, double exp) {
try {
return (double) powHandle.invokeExact(base, exp);
} catch (Throwable e) {
throw new RuntimeException("Native call failed", e);
}
}
@Override
public void close() {
arena.close();
}
// 使用示例
public static void main(String[] args) {
try (NativeMath math = new NativeMath("./libm.so")) {
System.out.println(math.sqrt(2.0)); // 1.414…
System.out.println(math.pow(2, 10)); // 1024.0
}
}
}
15.2 内存管理最佳实践
public class MemoryBestPractices {
// 实践1:始终使用try-with-resources
public void goodPractice() {
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg = arena.allocate(1024);
// 使用seg…
} // 自动释放,即使发生异常
}
// 实践2:批量分配,避免循环中频繁创建Arena
public void batchAllocation() {
try (Arena arena = Arena.ofConfined()) {
// 一次性分配所有需要的内存
MemorySegment buffer1 = arena.allocate(4096);
MemorySegment buffer2 = arena.allocate(4096);
MemorySegment buffer3 = arena.allocate(4096);
// 在循环中复用
for (int i = 0; i < 1000; i++) {
processWith(buffer1, buffer2, buffer3);
}
}
}
// 实践3:长生命周期数据用ofAuto
private static MemorySegment configData;
public static void loadConfig(byte[] rawData) {
Arena auto = Arena.ofAuto();
configData = auto.allocate(rawData.length);
MemorySegment.copy(rawData, 0, configData,
ValueLayout.JAVA_BYTE, 0, rawData.length);
// 无需关闭,GC管理
}
// 实践4:避免不必要的内存拷贝
public void zeroCopy(byte[] javaArray) {
// 直接包装Java数组(零拷贝)
MemorySegment view = MemorySegment.ofArray(javaArray);
// 修改view直接影响javaArray
view.set(ValueLayout.JAVA_BYTE, 0, (byte) 42);
System.out.println(javaArray[0]); // 42
}
private void processWith(MemorySegment a, MemorySegment b, MemorySegment c) {
// 处理逻辑
}
}
15.3 错误处理
public class ErrorHandling {
// 检查本地函数返回值
public static MemorySegment safeMalloc(long size) throws Throwable {
Linker linker = Linker.nativeLinker();
MethodHandle malloc = linker.downcallHandle(
linker.defaultLookup().find("malloc").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
);
MemorySegment ptr = (MemorySegment) malloc.invokeExact(size);
if (ptr.equals(MemorySegment.NULL)) {
throw new OutOfMemoryError("Native malloc failed for size: " + size);
}
return ptr;
}
// 检查errno(通过__errno_location或类似函数)
public static int getErrno() throws Throwable {
Linker linker = Linker.nativeLinker();
// Linux: int* __errno_location(void)
MethodHandle errnoLoc = linker.downcallHandle(
linker.defaultLookup().find("__errno_location").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS)
);
MemorySegment errnoPtr = (MemorySegment) errnoLoc.invokeExact();
return errnoPtr.reinterpret(4).get(ValueLayout.JAVA_INT, 0);
}
// 安全的字符串操作
public static String safeGetString(MemorySegment ptr, long maxLen) {
if (ptr.equals(MemorySegment.NULL)) {
return null;
}
MemorySegment bounded = ptr.reinterpret(maxLen);
return bounded.getString(0);
}
}
15.4 跨平台处理
public class CrossPlatform {
// 动态库文件扩展名
public static String libExtension() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) return ".dll";
if (os.contains("mac")) return ".dylib";
return ".so";
}
// 库文件路径
public static Path findLibrary(String baseName) {
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch");
String libName;
if (os.contains("win")) {
libName = baseName + ".dll";
} else if (os.contains("mac")) {
libName = "lib" + baseName + ".dylib";
} else {
libName = "lib" + baseName + ".so";
}
// 从资源目录或系统路径查找
Path localPath = Path.of("native", os.contains("win") ? "windows" :
os.contains("mac") ? "macos" : "linux", arch, libName);
if (java.nio.file.Files.exists(localPath)) {
return localPath.toAbsolutePath();
}
// 回退到系统搜索路径
return Path.of(libName);
}
// C long的平台差异处理
public static ValueLayout cLongLayout() {
// Windows 64位:long = 4 bytes (LLP64模型)
// Linux/Mac 64位:long = 8 bytes (LP64模型)
boolean isWindows = System.getProperty("os.name")
.toLowerCase().contains("win");
return isWindows ? ValueLayout.JAVA_INT : ValueLayout.JAVA_LONG;
}
}
15.5 性能优化清单
public class PerformanceTips {
/*
* 性能优化要点:
*
* 1. 缓存MethodHandle
* – 创建downcall handle成本高(~微秒级)
* – 用static final缓存,避免每次调用重建
*
* 2. 使用invokeExact而非invoke
* – invokeExact避免自动类型适配
* – 性能差距在热路径上显著
*
* 3. 使用VarHandle访问结构体
* – 避免每次通过byteOffset计算偏移
* – VarHandle可被JIT优化
*
* 4. 批量操作
* – 一次传递大数组,而非循环调用小数据
* – 减少Java↔Native边界跨越次数
*
* 5. Arena复用
* – 循环外创建Arena,循环内复用
* – 避免每次迭代都allocate/close
*
* 6. 避免不必要的reinterpret
* – 每次reinterpret创建新对象
* – 预先知道大小时直接分配正确大小
*
* 7. 选择合适的Arena类型
* – 单线程场景用ofConfined(无同步开销)
* – 避免在不需要共享时使用ofShared
*/
// 反面示例:每次调用都创建handle
public static int badAbs(int x) throws Throwable {
Linker linker = Linker.nativeLinker();
MethodHandle abs = linker.downcallHandle( // 每次创建!慢!
linker.defaultLookup().find("abs").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
);
return (int) abs.invoke(x); // invoke而非invokeExact!慢!
}
// 正面示例:缓存handle + invokeExact
private static final MethodHandle GOOD_ABS;
static {
try {
Linker linker = Linker.nativeLinker();
GOOD_ABS = linker.downcallHandle(
linker.defaultLookup().find("abs").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)
);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static int goodAbs(int x) throws Throwable {
return (int) GOOD_ABS.invokeExact(x); // 快速!
}
}
15.6 完整工程结构示例
my-native-project/
├── src/main/java/
│ └── com/example/
│ ├── NativeLib.java ← 封装层(对外API)
│ ├── NativeLibLoader.java ← 库加载逻辑
│ └── internal/
│ ├── bindings.java ← jextract生成的绑定
│ └── Layouts.java ← 自定义布局常量
├── src/main/resources/
│ └── native/
│ ├── linux/x86_64/libmylib.so
│ ├── windows/x86_64/mylib.dll
│ └── macos/aarch64/libmylib.dylib
├── native/
│ ├── mylib.h
│ ├── mylib.c
│ └── Makefile
├── build.gradle / pom.xml
└── README.md
总结
Project Panama 的 Foreign Function & Memory API 是 Java 平台在本地互操作领域的重大进步:
| 开发效率 | 纯Java编写,无需C胶水代码,jextract自动生成绑定 |
| 安全性 | 边界检查、Arena生命周期管理、防止内存泄漏 |
| 性能 | 调用开销降低5-10倍,零拷贝数据传输,JIT可优化 |
| 可维护性 | 统一调试、标准工具链、跨平台API |
适用场景:调用C/C++/Rust库、高性能计算、系统API访问、遗留代码集成。
JDK版本要求:JDK 22+(正式API),无需任何预览标志或额外模块。



