一、JNI核心定义与本质
JNI(Java Native Interface)是Java与本地代码(C/C++、汇编等)交互的桥梁,允许Java程序调用本地动态库中的代码,也支持本地代码反向访问Java层的字段与方法。其核心实现形式为:将JNI相关代码编写为汇编程序或C/C++程序,最终组装为平台适配的动态库(Windows下为.dll、Linux下为.so、Mac下为.jnilib)。
二、JNI开发完整流程
- 声明Native方法:在Java类中定义native关键字修饰的方法,作为Java与本地代码交互的接口。
-
编译Java源文件:通过
javac命令编译包含Native方法的Java源文件,生成.class字节码文件。 -
导出JNI头文件:执行
javah -jni命令,根据.class文件自动生成对应的JNI.h头文件,该文件包含Native方法的声明规范。 - 实现Native方法:使用C/C++等本地语言,按照头文件中的声明,实现Java层定义的Native方法。
- 编译动态库:将本地实现代码编译为对应操作系统的动态库文件(.dll/.so/.jnilib)。
- 执行Java程序:通过Java命令运行程序,Java层会加载动态库并调用本地方法,完成跨语言交互。
三、JNI核心语法与模板代码
(一)函数命名与关键字
-
函数名约定:Java的native方法对应的JNI函数名格式为
Java_[类的全限定名(带下划线)]_[方法名](类全限定名中的.替换为_)。 -
核心关键字:
-
JNIEXPORT:宏定义,标识函数需暴露给共享库外部使用,供Java层调用。 -
JNICALL:宏定义,标识该函数为JNI标准函数,用于规范函数调用约定。
-
(二)核心数据结构
| 结构名称 | 作用说明 | 特性 |
|---|---|---|
JavaVM |
代表Java虚拟机 | 每个Java进程仅有一个全局实例,支持跨线程共享 |
JNIEnv |
代表Java运行环境 | 每个Java线程独立拥有一个实例,不可跨线程共享,提供操作Java层的核心API |
jobject |
JNI层对Java应用类型对象的映射 | 对应Java层的Object类型,是引用数据类型的基础 |
(三)Java与C/C++类型映射规则
-
基础数据类型:直接映射为JNI定义的对应类型(如Java的
int→jint、boolean→jboolean),可直接作为普通C/C++变量使用,无需依赖JNIEnv。 -
引用数据类型:Java对象映射为C/C++指针类型(如Java的
Object→jobject、String→jstring),无法直接操作,必须通过JNIEnv提供的API进行访问。 -
关键注意点:
- 基础数据类型映射时不发生格式转换,例如Java的
char(2字节、UTF-16编码)映射为jchar后,长度和编码保持不变。 - 引用数据类型在本地方法执行期间,会被虚拟机自动“固定(pin)”,防止被GC回收,确保使用期间的有效性。
- 基础数据类型映射时不发生格式转换,例如Java的
四、JNI跨层交互核心操作
(一)访问Java字段
-
获取字段ID:通过
JNIEnv的GetFieldId方法,根据类对象(jclass)、字段名和字段签名,获取目标字段的唯一ID。
示例:jfieldID fid = env->GetFieldId(clz, "name", "Ljava/lang/String;"); -
操作字段值:通过字段ID调用对应API访问字段,如
GetObjectField(获取对象字段值)、SetIntField(设置基本类型字段值)等。
示例:jstring jstr = env->GetObjectField(thiz, fid);
(二)调用Java方法
-
获取方法ID:通过
JNIEnv的GetMethodID(实例方法)或GetStaticMethodID(静态方法),根据类对象、方法名和方法签名,获取目标方法的唯一ID。
示例:jmethodID mid = env->GetMethodID(jclass, "helloJava", "()V"); -
执行方法调用:通过方法ID调用对应API执行方法,如
CallVoidMethod(无返回值实例方法)、CallIntMethod(返回int类型的实例方法)等。
示例:env->CallVoidMethod(thiz, mid);
五、JNI对象引用管理
(一)局部引用(Local Reference)
- 生成场景:通过
NewObject等JNI函数创建Java对象时返回的引用,或传入JNI方法的jobject参数。 - 生命周期:默认在当前JNI函数返回后自动释放,也可通过
DeleteLocalRef方法手动显式释放,避免内存泄漏。 - 特性:仅在当前线程、当前函数中有效,不可跨线程或跨函数使用。
(二)全局引用(Global Reference)
- 生成场景:通过
NewGlobalRef函数将局部引用升级为全局引用,用于长期持有Java对象。 - 生命周期:需手动通过
DeleteGlobalRef方法释放,否则会导致内存泄漏(类似Java中静态变量持有对象)。 - 特性:可跨线程、跨函数使用,适用于需要长期复用的Java对象。
六、JNI异常处理机制
(一)异常抛出与特性
- JNI层通过
ThrowNew函数抛出异常,但不会立即中断当前执行流程,需返回Java层后,由虚拟机触发异常抛出。 - 异常未处理时,仅允许调用“异常处理函数”(如
ExceptionClear)和“资源清理函数”,其他JNI函数调用会失效。
(二)异常处理方式
- 方式一:抛给Java层处理:直接返回当前JNI方法,不处理异常,由Java层通过try-catch捕获并处理(类似Java的异常向上抛出)。
-
方式二:JNI层本地处理:
- 调用
ExceptionClear函数清除当前异常; - 执行自定义的异常处理逻辑(如资源回滚、日志记录);
- 确保异常处理完成后,再执行其他JNI操作。
- 调用