JNI(Java Native Interface)是Java平台提供的一种机制,允许Java代码与其他语言(主要是C/C++)编写的代码进行交互。这种技术广泛应用于需要高性能计算、硬件操作或复用已有本地代码库的场景。下面我将从原理到实践全面解析JNI技术。
JNI是Java Native Interface的缩写,它建立了一个桥梁,使得Java代码能够调用本地方法(Native Method),同时本地代码也可以调用Java对象和方法。这种双向交互能力使得Java可以突破自身限制,实现以下功能:
JNI通过特定的命名规则和数据类型映射实现Java与本地代码的交互:
native
关键字声明本地方法System.loadLibrary()
加载动态链接库图1:JNI调用流程
Java Code → JNI Interface → Native Code (C/C++)
虽然现在官方推荐使用javac -h
,但理解传统流程有助于掌握原理:
javapublic class HelloJNI {
static {
System.loadLibrary("hello"); // 加载动态库
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello();
}
}
javah
命令javac HelloJNI.java javah HelloJNI
生成的头文件包含类似下面的函数声明:
cJNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
c#include "HelloJNI.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
bash# Linux
gcc -shared -fPIC -o libhello.so HelloJNI.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
# Windows
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloJNI.c -Fehello.dll
现在官方推荐使用javac -h
生成头文件:
javapackage com.pyc.jni;
public class JNIDemo {
public native void testHello();
public static void main(String[] args) {
System.loadLibrary("TestJNI");
new JNIDemo().testHello();
}
}
bashjavac -h . com/pyc/jni/JNIDemo.java
这会生成com_pyc_jni_JNIDemo.h
头文件
jni.h
和jni_md.h
到项目libTestJNI.dylib
或TestJNI.dll
)JNI定义了Java类型与本地类型的对应关系:
表1:JNI基本类型映射
Java类型 | JNI类型 | 描述 |
---|---|---|
boolean | jboolean | C/C++ 8位整型 |
byte | jbyte | 带符号的8位整型 |
char | jchar | 无符号的16位整型 |
short | jshort | 带符号的16位整型 |
int | jint | 带符号的32位整型 |
long | jlong | 带符号的64位整型 |
float | jfloat | 32位浮点型 |
double | jdouble | 64位浮点型 |
Object | jobject | 任何Java对象 |
String | jstring | Java字符串对象 |
Array | jarray | Java数组 |
JNIEnv
是指向JNI函数表的指针,它提供了以下功能:
在C和C++中使用方式不同:
c// C风格
(*env)->NewStringUTF(env, "Hello");
// C++风格
env->NewStringUTF("Hello");
JNI不仅允许Java调用本地代码,还支持本地代码回调Java方法:
cppjclass clazz = env->FindClass("com/example/Test");
cppjmethodID method = env->GetMethodID(clazz, "add", "(II)I");
其中"(II)I"
是方法签名,表示两个int参数和int返回值
cppjint result = env->CallIntMethod(obj, method, 10, 20);
JNI中的异常处理需要注意:
cppjthrowable exc = env->ExceptionOccurred();
if (exc) {
env->ExceptionDescribe();
env->ExceptionClear();
}
cppjclass excCls = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(excCls, "Null pointer exception");
JNI涉及Java与本地内存交互,需要注意:
cppjclass globalClazz = (jclass)env->NewGlobalRef(localClazz);
cppenv->DeleteGlobalRef(globalClazz);
ByteBuffer.allocateDirect()
虽然JNI功能强大,但也有更简单的替代方案:
表2:JNI与替代技术对比
特性 | JNI | JNA | SWIG |
---|---|---|---|
需要本地代码 | 是 | 否 | 部分 |
性能 | 最高 | 中等 | 高 |
复杂度 | 高 | 低 | 中等 |
适用场景 | 高性能需求 | 快速集成 | 多语言项目 |
UnsatisfiedLinkError
-Djava.library.path
)NewGlobalRef
/DeleteGlobalRef
管理引用AttachCurrentThread
附加线程DetachCurrentThread
分离JNI作为Java与本地代码交互的标准接口,虽然学习曲线较陡峭,但掌握后可以极大扩展Java的能力边界。在实际开发中,应根据项目需求选择合适的技术方案,平衡开发效率与运行性能。