【FFI】 Java 侧演化——从 JNI 到 Panama
Java 从 JDK 1.1 就能调 C,但这条路走了二十多年,一直在"方便"和"快"之间做取舍。四个方案串起来看,就是 Java FFI 的演化史。
JNI:最底层,也最麻烦
JNI(Java Native Interface)是 Java 调 C 的标准姿势。Java 侧写 native 方法,javac -h 生成 C 头文件,手写 C 实现:
// Java 侧
public class NativeLib {
static { System.loadLibrary("mylib"); }
public static native int fib(int n);
}
// C 侧,按 JNI 规范生成的函数名
JNIEXPORT jint JNICALL
Java_com_example_NativeLib_fib(JNIEnv *env, jclass cls, jint n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
C 函数名是 Java_<包名>_<类名>_<方法名>,参数里多了一个 JNIEnv*(JVM 的指针,用来操作 Java 对象)和 jclass(调用类的 class 对象)。JNI 是静态绑定:编译期生成桩,调用开销低,但写起来极其繁琐——改个方法名要重新生成头文件、改个参数类型要重新编译 C 代码。
JNA:站在 libffi 上,无需编译
JNA(Java Native Access)把 JNI 的编译期拉到了运行时。它站在 libffi 上,不需要生成头文件:
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface MyLib extends Library {
MyLib INSTANCE = Native.load("mylib", MyLib.class);
int fib(int n);
}
// 直接调
MyLib.INSTANCE.fib(40);
你定义一个 Java interface 映射 C 函数签名,JNA 运行时用 libffi 构造调用栈。不用写 C、不用 javac -h、不用编译 .so。代价是每次调用都要解析签名、marshal 参数,比 JNI 慢一截。适合探索性开发和胶水脚本。
JNR:JNA 的继任者
JNR(Java Native Runtime)是 JNA 的下一代。同样是动态绑定,但用了更激进的技术——运行时直接生成 JIT 编译的 native stub,不用 libffi 的通用栈帧构造,性能接近 JNI。
代码风格和 JNA 类似,但生态没跟上,用户量远不如 JNA。现在基本是 JNA 和 Panama 二分天下。
Project Panama:官方下场,一统江湖
Panama 是 JDK 16 开始孵化的官方方案,JDK 19 正式发布,目标就是替代 JNI。
核心理念:jextract 工具从 C 头文件自动生成 Java 绑定,不用手写任何 C 代码。
# 从 C 头文件生成 Java 绑定
jextract mylib.h --output target/classes
生成的 Java 代码用 MemorySegment 和 MethodHandle 直接操作 native 内存:
// Panama 生成的安全内存访问
MemorySegment segment = Arena.ofAuto().allocate(4);
segment.set(ValueLayout.JAVA_INT, 0, 42);
int val = segment.get(ValueLayout.JAVA_INT, 0);
MemorySegment 是 Panama 最大的区别——它提供了安全的 native 内存访问。JNI 里一个 void* 指针随时可能变成野指针,Panama 用 Arena.ofAuto() 自动管理生命周期:Arena 关掉,所有从它分配的内存一起释放。不用手写 free,不用怕 double free。
演进逻辑
| 方案 | 绑定时机 | 胶水代码 | 性能 | 内存安全 |
|---|---|---|---|---|
| JNI | 编译期 | 手写 C 头文件 | 快 | 全靠人肉 |
| JNA | 运行时(libffi) | 只写 Java | 慢 | 全靠人肉 |
| Panama | 编译期(jextract) | 自动生成 | 快 | Arena 自动管理 |
从 JNI 到 Panama,Java FFI 走了二十多年,核心演进方向只有一个:把"手写 C"这件事干掉。JNI 让你手写 C,JNA 让你只写 Java,Panama 让你什么都不写——jextract 从 .h 文件直接生成,自动处理类型映射、内存安全、调用约定。和 PyO3 异曲同工:编译期把事情做完,运行时只管调用。
寒蝉 Hancic

