java 21的ffm api通过symbollookup和methodhandle调用c函数,需显式加载库、严格匹配类型、用arena管理内存,不兼容jni或system.loadlibrary。

Java 21里怎么用Foreign Function & Memory API调C函数
它不是JNI的“升级版”,而是彻底换了一套模型:不靠jni.h头文件和JNIEnv*指针,改用描述式声明 + 运行时绑定。你得先定义SymbolLookup找函数地址,再用MethodHandle封装调用逻辑。
常见错误是直接拿System.loadLibrary("xxx")就想用——这招在FFM API里完全无效,库必须通过Linker.nativeLinker().defaultLookup()或LibraryLookup.ofPath()显式加载。
-
Linker.nativeLinker()返回平台相关实现(Windows用Windowsx64Linker,Linux用LinuxAArch64Linker),不能跨平台硬编码 - 函数签名必须严格匹配:C的
int32_t对应ValueLayout.JAVA_INT,char*要用MemorySegment加UTF_8编码,不能直接传String - 调用后记得
scope.close()释放内存段,否则可能触发IllegalStateException: Scope is already closed
MethodHandle strlen = linker.downcallHandle(
lookup.find("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);
MemorySegment str = arena.allocateUtf8String("hello");
long len = (long) strlen.invokeExact(str);
MemorySegment和ByteBuffer哪个更适合做C内存桥接
MemorySegment是FFM API的基石,ByteBuffer只是兼容层;前者能直接映射文件、堆外内存、甚至C malloc出来的地址,后者只能处理JVM堆内或DirectBuffer。
容易踩的坑是误以为MemorySegment.ofByteBuffer()能双向同步修改——其实它只读取初始状态,后续C端改了内存,Java端不会自动感知;反之亦然。
立即学习“Java免费学习笔记(深入)”;
- 需要实时共享内存时,必须用
MemorySegment.mapFile()或MemorySegment.allocateNative()配Arena生命周期管理 -
ByteBuffer传给C函数前,得用MemorySegment.ofByteBuffer(bb).address()取地址,别直接传bb.array()(可能抛UnsupportedOperationException) - 涉及结构体嵌套指针(比如
struct node { int val; struct node* next; }),MemorySegment支持reinterpret()和asSlice(),ByteBuffer做不到
为什么用Arena替代Unsafe.allocateMemory
Arena不是语法糖,它是资源生命周期的强制约束机制。以前用Unsafe.allocateMemory分配的内存,全靠程序员手动free,漏掉就内存泄漏;现在所有MemorySegment都绑定到某个Arena,close()一调,整片内存连带子段全部释放。
但要注意:Arena.ofConfined()适合短时局部使用(如单次C函数调用),Arena.ofShared()能跨线程但需自行同步,Arena.ofAuto()看似方便,实际在循环中反复创建会触发GC压力。
- 不要在lambda里捕获
Arena并异步使用——close()可能提前发生,导致IllegalStateException: Segment is already closed - 结构体数组初始化推荐用
arena.allocateArray(ValueLayout.JAVA_INT, 10),比循环allocate()更安全 -
Arena本身不可序列化,别试图存进Redis或网络传输
FFM API在GraalVM Native Image里怎么跑
默认编译会丢掉所有原生符号,SymbolLookup.find()必然返回Optional.empty()。必须显式告诉GraalVM哪些库、哪些函数要保留。
关键不是加--enable-all-security-services这种大而全参数,而是精准配置native-image.properties或命令行--initialize-at-run-time。
- 动态库路径要用绝对路径,相对路径在Native Image里解析失败,报
java.lang.UnsatisfiedLinkError: unable to locate library - C函数名若含下划线(如
my_func),GraalVM默认按C++符号修饰规则处理,需加@CName("my_func")注解 -
MemorySegment.mapFile()在Native Image中受限,建议改用Arena.allocateNative()+ 手动memcpy
最常被忽略的是:FFM API依赖jdk.incubator.foreign模块,在native-image命令里必须显式加--add-modules jdk.incubator.foreign,否则编译期就报错找不到类。









