JNI 是 Java 與本地代碼 (C/C++/Assembly) 交互的橋樑。本文系統梳理 聲明 native 方法 → javah 生成頭文件 → C/C++ 實現 → 編譯動態庫 → Java 加載調用 全流程,並補充異常處理、引用管理、性能優化、線程附着、構建系統集成等生產級關鍵點。
public class SmsSender {
static { System.loadLibrary("smssender"); } // 加載 libsmssender.so / smssender.dll
public native int sendSms(String phone, String content); // 僅聲明
public static void main(String[] args) {
System.out.println(new SmsSender().sendSms("13800000000", "Hello JNI"));
}
}
javac SmsSender.java 生成 SmsSender.classjavah -jni SmsSender 生成 SmsSender.h
/* DO NOT EDIT - machine generated */ #include#ifndef _Included_SmsSender #define _Included_SmsSender #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_SmsSender_sendSms (JNIEnv *, jobject, jstring, jstring); #ifdef __cplusplus } #endif #endif
SmsSender.c):
#include "SmsSender.h" #includeJNIEXPORT jint JNICALL Java_SmsSender_sendSms (JNIEnv *env, jobject obj, jstring phone, jstring content) { // 1. 將 jstring 轉為 C 字符串 const char *cPhone = (*env)->GetStringUTFChars(env, phone, NULL); const char *cContent = (*env)->GetStringUTFChars(env, content, NULL); // 2. 調用本地短信庫 API int result = sms_api_send(cPhone, cContent); // 3. 釋放字符串資源(必須成對調用) (*env)->ReleaseStringUTFChars(env, phone, cPhone); (*env)->ReleaseStringUTFChars(env, content, cContent); return result; }
gcc -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux SmsSender.c -o libsmssender.socl /LD /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" SmsSender.c /Fesmssender.dlljava.library.path (或 LD_LIBRARY_PATH/PATH) 中,java SmsSender| Java 類型 | JNI 類型 | C 類型 | 描述 |
|---|---|---|---|
| boolean | jboolean | unsigned char | 8 位 |
| byte | jbyte | signed char | 8 位 |
| char | jchar | unsigned short | 16 位 Unicode |
| short | jshort | short | 16 位 |
| int | jint | int32_t | 32 位 |
| long | jlong | int64_t | 64 位 |
| float | jfloat | float | 32 位 |
| double | jdouble | double | 64 位 |
| Object | jobject | _jobject* | 引用 |
| String | jstring | _jstring* | 引用 |
| int[] | jintArray | _jarray* | 引用 |
// 字符串處理 const char* GetStringUTFChars(JNIEnv*, jstring, jboolean* isCopy); void ReleaseStringUTFChars(JNIEnv*, jstring, const char*); jstring NewStringUTF(JNIEnv*, const char*); // 數組處理 jint* GetIntArrayElements(JNIEnv*, jintArray, jboolean*); void ReleaseIntArrayElements(JNIEnv*, jintArray, jint*, jint mode); // mode: 0/JNI_COMMIT/JNI_ABORT jintArray NewIntArray(JNIEnv*, jsize); void SetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, jint*); // 類與方法 jclass FindClass(JNIEnv*, const char*); jmethodID GetMethodID(JNIEnv*, jclass, const char*, const char*); // 實例方法 jmethodID GetStaticMethodID(JNIEnv*, jclass, const char*, const char*); jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...); jint CallIntMethod(JNIEnv*, jobject, jmethodID, ...); void CallVoidMethod(JNIEnv*, jobject, jmethodID, ...); // 異常處理 jthrowable ExceptionOccurred(JNIEnv*); void ExceptionClear(JNIEnv*); void ThrowNew(JNIEnv*, jclass, const char*); // 引用管理 jobject NewGlobalRef(JNIEnv*, jobject); // 跨調用持久引用 void DeleteGlobalRef(JNIEnv*, jobject); jobject NewLocalRef(JNIEnv*, jobject); // 手動創建局部引用 void DeleteLocalRef(JNIEnv*, jobject); // 手動釋放(防止局部引用表溢出)
ExceptionOccurred(),拋出 Java 異常而非返回錯誤碼GetStringUTFChars 必配 ReleaseStringUTFChars;數組元素同理;使用 try/finally 模式在 C 中模擬DeleteLocalRef 清理;全局引用必須 DeleteGlobalRef 釋放(*jvm)->AttachCurrentThread(jvm, &env, NULL),結束前 DetachCurrentThreadjclass、jmethodID、jfieldID(靜態變量,初始化一次)GetIntArrayRegion/SetIntArrayRegion 避免逐元素調用byte[] 或 ByteBuffer (直接內存)src/main/jniLibs/${arch}/printf + fflush(stdout);GDB/LLDB attach 到 Java 進程;-Xcheck:jni 啟動參數檢測 JNI 錯誤| 問題 | 原因 | 解決 |
|---|---|---|
| UnsatisfiedLinkError | 庫未找到、方法簽名不匹配、依賴庫缺失 | 檢查 java.library.path、javah 重新生成頭文件、ldd/Dependency Walker 檢查依賴 |
| JNI WARNING: local reference table overflow | 循環中大量創建局部引用未刪除 | 循環內調用 DeleteLocalRef 或使用 PushLocalFrame/PopLocalFrame |
| Crash in GetStringUTFChars | 傳入 null jstring | 調用前檢查 jstring != NULL |
| 多線程 Crash | 非附着線程使用 JNIEnv | AttachCurrentThread 獲取專屬 JNIEnv |
| 內存洩漏 | 未 Release 數組/字符串、未 DeleteGlobalRef | 嚴格配對、使用 RAII 封裝類 |
JNI 是 Java 突破沙箱、復用 C/C++ 生態、實現高性能計算的關鍵技術。掌握 類型映射、引用管理、異常傳播、線程模型、構建集成 是編寫穩健本地庫的基礎。新項目優先考慮 JNA 或 Panama API 降低複雜度;性能極度敏感、需精細控制內存佈局、對接現有 C++ 庫時,JNI 仍是最佳選擇。