java通过jni建立jvm与c库的受控双向通道,核心是契约约定(签名对齐与符号匹配)、运行时桥接(jnienv线程独有翻译官)和资源隔离(内存独立、引用需显式管理)。

Java 通过 JNI(Java Native Interface)在 JVM 和系统 C 库之间建立明确、受控的双向通道,不是“穿透”或“绕过”JVM,而是由 JVM 主动提供接口、管理生命周期、约束行为。整个过程围绕三个核心环节展开:**契约约定、运行时桥接、资源隔离**。
契约约定:Java 与 C 的签名对齐
Java 端用
native 关键字声明方法,不写实现;
C 端按固定命名规则(如
JNIEnv *env, jobject thisObj)实现函数,并包含
jni.h;
编译生成动态库(
.so 或
.dll)后,Java 用
System.loadLibrary("xxx") 触发加载;
JVM 在运行时根据方法签名自动查找并绑定对应 C 函数——这一步靠的是符号表匹配,不是反射或动态解析。
运行时桥接:JNIEnv 是唯一合法“翻译官”
每次 Java 调用 native 方法,JVM 都会传入一个
JNIEnv* 指针;
它不是全局变量,而是**当前线程独有**的结构体指针,内含函数表(如
NewStringUTF、
GetIntField、
ThrowNew);
所有 Java 对象访问、类型转换、异常抛出、数组操作,都必须通过这个指针调用 JNI 函数完成;
直接操作 Java 对象内存地址、缓存
jstring 原生指针、跨线程复用
JNIEnv*,都会导致崩溃或未定义行为。
资源隔离:内存与生命周期各自负责
Java 堆内存由 GC 管理,C 侧 malloc 分配的内存完全独立,JVM 不感知、不回收;
C 函数中若返回字符串、数组等数据,必须用 JNI 函数(如
GetStringUTFChars +
ReleaseStringUTFChars)成对拷贝或引用;
Java 对象传入 C 后,局部引用(LocalRef)默认只在本次 native 方法内有效;需长期持有,必须调用
NewGlobalRef 并显式
DeleteGlobalRef;
DirectByteBuffer 是特例:其底层 native 内存由 JVM 统一管理,GC 可触发释放,适合大块数据交换。
启动与初始化:JNI_OnLoad 是可信入口
当
System.loadLibrary 执行时,JVM 会查找并调用 C 库中的
JNI_OnLoad 函数;
它用于:向 JVM 声明所需 JNI 版本(如
JNI_VERSION_1_8),注册本地方法表(避免运行时符号查找开销),执行 C 侧一次性的初始化(如打开设备句柄、初始化线程池);
没有
JNI_OnLoad,JVM 默认使用最老版本,部分新特性不可用;
同理,
JNI_OnUnload 可用于清理资源,但仅在 JVM 卸载该库时调用(通常发生在应用关闭阶段)。
不复杂但容易忽略