JNI 架構總覽
Java 代碼 JNI 層 C/C++ 實現
┌─────────────┐ ┌──────────────┐ ┌────────────────┐
│ native 方法 │───▶│ JNIEnv 接口 │───▶│ .dll / .so 庫 │
│ System.load │ │ 類/方法簽名 │ │ 具體邏輯實現 │
└─────────────┘ └──────────────┘ └────────────────┘
Java 端開發步驟
1. 宣告 native 方法並載入庫
public class NativeDemo {
// 靜態塊載入庫 (無副檔名,跨平台)
static {
System.loadLibrary("nativeDemo"); // Windows: nativeDemo.dll, Linux: libnativeDemo.so
// 或指定絕對路徑: System.load("/path/libnativeDemo.so");
}
// 宣告 native 方法 (僅簽名,無實現)
public native static int getValue();
public native static void setValue(int value);
public native String processData(String input); // 實例方法
public static void main(String[] args) {
NativeDemo demo = new NativeDemo();
demo.setValue(42);
System.out.println("Value: " + demo.getValue());
System.out.println("Processed: " + demo.processData("Hello JNI"));
}
}
2. 編譯產生 .class
javac NativeDemo.java
3. 生成 C/C++ 標頭檔
# JDK 8-10
javah NativeDemo
# JDK 10+ (javah 移除,用 javac -h)
javac -h . NativeDemo.java
生成的標頭檔 (NativeDemo.h)
/* DO NOT EDIT - machine generated */
#include
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
// 靜態方法: Java_類全名_方法名
JNIEXPORT jint JNICALL Java_NativeDemo_getValue
(JNIEnv *, jclass);
// 靜態方法帶參數
JNIEXPORT void JNICALL Java_NativeDemo_setValue
(JNIEnv *, jclass, jint);
// 實例方法
JNIEXPORT jstring JNICALL Java_NativeDemo_processData
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
C/C++ 端實現
資料類型映射表
| 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 | 32 位 |
| long | jlong | long long / int64_t | 64 位 |
| float | jfloat | float | 32 位 |
| double | jdouble | double | 64 位 |
| String | jstring | 不透明引用 | 需 JNI 函數轉換 |
| Object | jobject | 不透明引用 | 所有物件基類 |
| Class | jclass | jobject 子類 | 類物件 |
| int[] | jintArray | jobject 子類 | 陣列類型 |
實現檔案
#include "NativeDemo.h"
#include
#include
// 全域狀態 (對應 Java static 欄位)
static int g_value = 0;
// 靜態方法: 參數 (JNIEnv*, jclass)
JNIEXPORT jint JNICALL Java_NativeDemo_getValue(JNIEnv *env, jclass cls) {
return g_value;
}
JNIEXPORT void JNICALL Java_NativeDemo_setValue(JNIEnv *env, jclass cls, jint value) {
g_value = value;
}
// 實例方法: 參數 (JNIEnv*, jobject, ...)
JNIEXPORT jstring JNICALL Java_NativeDemo_processData(JNIEnv *env, jobject obj, jstring input) {
// 1. jstring → C 字串 (UTF-8)
const char *cstr = (*env)->GetStringUTFChars(env, input, NULL);
if (cstr == NULL) return NULL; // OOM
// 2. 業務邏輯
char buffer[256];
snprintf(buffer, sizeof(buffer), "Processed: %s", cstr);
// 3. 釋放 C 字串資源
(*env)->ReleaseStringUTFChars(env, input, cstr);
// 4. C 字串 → jstring
return (*env)->NewStringUTF(env, buffer);
}
編譯生成動態庫
Windows (MSVC)
cl /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" /LD NativeDemo.c /Fe nativeDemo.dll
Linux/macOS (GCC/Clang)
gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
NativeDemo.c -o libnativeDemo.so
庫檔案放置
- Java 類同目錄
- 或
java.library.path 指定目錄:java -Djava.library.path=/path/to/lib NativeDemo
- 或
System.load("/absolute/path/libnativeDemo.so")
進階主題
陣列操作
// Java: native int sumArray(int[] arr);
JNIEXPORT jint JNICALL Java_NativeDemo_sumArray(JNIEnv *env, jclass cls, jintArray arr) {
jint *elements = (*env)->GetIntArrayElements(env, arr, NULL);
jsize len = (*env)->GetArrayLength(env, arr);
jint sum = 0;
for (int i = 0; i < len; i++) sum += elements[i];
(*env)->ReleaseIntArrayElements(env, arr, elements, 0); // 0: 複製回並釋放
return sum;
}
回調 Java 方法
// 獲取類引用
jclass cls = (*env)->GetObjectClass(env, obj);
// 獲取方法 ID
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
// 呼叫
(*env)->CallVoidMethod(env, obj, mid, 42);
異常處理
// 拋出 Java 異常
jclass excCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
(*env)->ThrowNew(env, excCls, "Invalid input from native code");
建構系統整合
Maven (maven-nar-plugin)
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>maven-nar-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<libraries>
<library>
<type>jni</type>
</library>
</libraries>
</configuration>
</plugin>
Gradle (native 套件)
plugins { id 'cpp-library' }
library {
targetMachines.add(machines.linux.x86_64, machines.windows.x86_64, machines.mac.x86_64)
// JNI 專用配置...
}
現代替代方案對比
| 方案 | 優點 | 缺點 | 適用場景 |
| JNI | 標準、效能最高、完全控制 | 複雜、易崩潰、跨平台編譯麻煩 | 高效能、現有 C/C++ 庫、硬體驅動 |
| JNA | 純 Java、無需編譯 C、易用 | 效能較 JNI 低 10-100x | 快速原型、系統 API 呼叫、非核心路徑 |
| JNR-FFI | 效能接近 JNI、API 現代 | 社群較小 | 需平衡效能與開發效率 |
| GraalVM Native Image | Java 直接編譯原生、互操作性強 | 編譯慢、限制多 | 雲原生、Serverless、微服務 |
| Panama (JDK 19+) | Foreign Function & Memory API、標準化 | 預覽/孵化階段 | 未來標準、替代 JNI/JNA |
總結
JNI 是 Java 與本地代碼橋樑,核心流程:native 宣告 → javah 生成頭文件 → C/C++ 實現 → 編譯動態庫 → System.loadLibrary 載入。關鍵難點在於資料類型映射、字串/陣列轉換、記憶體管理、異常傳遞。現代開發優先評估 JNA/Panama,僅在極致效能或現有 C/C++ 資產整合時使用 JNI。理解 JNI 內部機制,有助於排查 JVM 崩潰、記憶體洩漏、效能抖動等疑難雜症。