Java 「一次編寫,到處運行」的代價是無法直接訪問平台特有功能(硬件驅動、系統調用、高性能數學庫、舊有 C/C++ 資產)。JNI(Java Native Interface)打通了 Java 虛擬機與本地代碼的雙向通道,是 Java 生態連接底層世界的標準橋樑。
本地方法在 .c/.cpp 實現,函數簽名遵循固定模式:
JNIEXPORT void JNICALL
Java_包名_類名_方法名(JNIEnv *env, jobject obj /*, 參數...*/) {
// 本地實現
}
JNIEnv*:JVM 接口指針,提供 200+ 操作函數(物件操作、異常拋出、類加載、反射等)jobject:調用對象引用(實例方法為 this,靜態方法為 Class 對象)JNIEnv 實際是 **指向函數指針表的指針**(雙重指針)。每個線程擁有獨立 JNIEnv,不可跨線程共享。JVM 保證同一線程多次調用同一方法時傳遞同一 JNIEnv。
// Java 端
public class NativeDemo {
static {
System.loadLibrary("native-demo"); // 載入 libnative-demo.so / native-demo.dll
}
native void nativeMethod();
}
庫命名規則:System.loadLibrary("name") → Linux libname.so、Windows name.dll、macOS libname.dylib。
| Java 類型 | JNI 類型 | C/C++ 對應 | 備註 |
|---|---|---|---|
| boolean | jboolean | unsigned char | 8 位 |
| byte | jbyte | signed char | 8 位 |
| char | jchar | unsigned short | 16 位 Unicode |
| short | jshort | short | 16 位 |
| int | jint | int / long | 32 位 |
| long | jlong | long long / int64_t | 64 位 |
| float | jfloat | float | 32 位 IEEE 754 |
| double | jdouble | double | 64 位 IEEE 754 |
| Object | jobject | _jobject* | 引用傳遞 |
| String | jstring | _jstring* | 需轉換 |
| int[] | jintArray | _jintArray* | 數組需轉換 |
基本類型按值傳遞,引用類型按引用傳遞。jint 與 int 可直接互用無需轉換。
JNIEXPORT void JNICALL
Java_Demo_processString(JNIEnv *env, jobject obj, jstring javaStr) {
// 獲取 UTF-8 C 字符串(可能拷貝、可能釘住)
const char *cStr = (*env)->GetStringUTFChars(env, javaStr, NULL);
if (cStr == NULL) return; // OOM
printf("Received: %s\n", cStr);
// 必須釋放,否則內存洩漏或釘住對象阻礙 GC
(*env)->ReleaseStringUTFChars(env, javaStr, cStr);
}
⚠️ 絕對不能 直接操作 jstring 內存,也不能緩存 GetStringUTFChars 返回的指針跨調用使用。
// 基本類型數組:GetArrayElements / ReleaseArrayElements
jintArray javaArr = ...;
jint *cArr = (*env)->GetIntArrayElements(env, javaArr, NULL);
// 使用 cArr...
(*env)->ReleaseIntArrayElements(env, javaArr, cArr, 0); // mode=0: 複製回並釋放
// 大數組高性能場景:GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical
// 禁止在 critical 區塊調用 JNI 函數、分配內存、阻塞操作
GetFieldID → GetField /SetField GetMethodID → CallMethod NewObject、NewObjectArrayThrowNew、ExceptionCheck、ExceptionClearNewGlobalRef)必須手動 DeleteGlobalRefExceptionCheck()AttachCurrentThreadJNI 是把雙刃劍:賦予 Java 無限可能,也引入內存安全、線程模型、調試困難等挑戰。現代替代方案(JNA、JNR、GraalVM Native Image、FFI)在大多數場景下更安全易用。**除非極致性能或硬性依賴本地庫,優先考慮純 Java 或高層封裝庫。**