1 什麼是本地方法
首先要知道什麼是本地方法,本地方法並不是 JVM 自己的方法,也不是 jre 裡面的方法,而是指那些作業系統自己的方法(如C/C++方法),它們在作業系統目錄裡。可以這麼理解,本地方法就是計算機作業系統對外提供的方法,JVM 透過呼叫這些方法可以實現 Java 程式和計算機的互動。
1.1 本地方法的好處
- 訪問作業系統資源:直接呼叫作業系統的API,例如檔案系統、網路介面、圖形使用者介面等。
- 效能最佳化:對於某些計算密集型任務,使用C或C++等語言實現可以顯著提高效能。
- 使用現有庫:利用已經存在的C/C++庫,避免重複開發和維護。
- 硬體訪問:直接訪問硬體裝置,例如攝像頭、感測器等。
所以如果我們想最佳化計算密集型任務的效能,或是呼叫 Java 中沒有實現的計算機功能,我們可以自己實現一個本地方法。
1.2 宣告本地方法
在Java中,本地方法透過native
關鍵字宣告。例如:
public class MyClass {
// 宣告本地方法
public native void nativeMethod();
// 靜態塊中載入本地庫
static {
System.loadLibrary("mylib"); // 載入名為mylib的本地庫
}
public static void main(String[] args) {
new MyClass().nativeMethod(); // 呼叫本地方法
}
}
1.3 實現本地方法
本地方法的實現通常使用JNI(Java Native Interface)或JNA(Java Native Access)來完成。以下是使用JNI實現本地方法的步驟:
1. 生成標頭檔案
使用javah
工具生成包含本地方法簽名的C標頭檔案。假設上面的Java類儲存為MyClass.java
,編譯後生成MyClass.class
,然後執行:
javah -jni MyClass
這將生成一個名為MyClass.h
的標頭檔案,內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyClass */
#ifndef _Included_MyClass
#define _Included_MyClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MyClass
* Method: nativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MyClass_nativeMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
2. 編寫C語言實現
根據生成的標頭檔案,編寫C語言實現。例如:
#include <jni.h>
#include <stdio.h>
#include "MyClass.h"
// 實現本地方法
JNIEXPORT void JNICALL Java_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
printf("Hello from native method!\n");
}
3. 編譯C程式碼
將C程式碼編譯成動態連結庫。假設C檔名為mylib.c
,編譯命令如下:
在Linux上:
gcc -shared -o libmylib.so -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux mylib.c
在Windows上:
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD mylib.c -Fe mylib.dll
4. 執行Java程式
確保動態連結庫在Java程式的庫路徑中,然後執行Java程式:
java -Djava.library.path=. MyClass
1.4 使用JNA
JNA是一種更簡單的方式來呼叫本地庫,不需要編寫C程式碼。以下是一個使用JNA的示例:
- 新增JNA依賴:在專案中新增JNA的依賴。如果你使用Maven,可以在
pom.xml
中新增:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.8.0</version>
</dependency>
- 定義介面:定義一個介面來對映本地庫中的函式。
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface MyLib extends Library {
MyLib INSTANCE = Native.load("mylib", MyLib.class);
void nativeMethod();
}
- 呼叫本地方法:
public class MyClass {
public static void main(String[] args) {
MyLib.INSTANCE.nativeMethod(); // 呼叫本地方法
}
}
1.5 總結
本地方法是Java程式中的一種特殊方法,其宣告在Java程式碼中,但實現由非Java語言編寫。透過本地方法,Java程式可以訪問作業系統資源、最佳化效能、使用現有庫和直接訪問硬體裝置。常見的實現方式包括JNI和JNA。
2 本地方法棧
2.1 特點
- 執行緒私有:每個執行緒都有自己的本地方法棧,與Java虛擬機器棧一樣,本地方法棧也是執行緒私有的。
- 儲存結構:本地方法棧中的每個棧幀(Frame)對應一次本地方法的呼叫。棧幀中包含本地方法的引數、區域性變數、運算元棧等資訊。
- 呼叫機制:當Java程式碼呼叫一個本地方法時,JVM會建立一個新的棧幀並將其壓入本地方法棧。本地方法執行完畢後,棧幀會被彈出並丟棄。
2.2 本地方法棧與Java虛擬機器棧的區別
- 用途不同:Java虛擬機器棧用於支援Java方法的執行,而本地方法棧用於支援本地方法的執行。
- 實現方式:Java虛擬機器棧的實現由JVM規範規定,而本地方法棧的實現通常依賴於具體的JVM實現和作業系統的ABI(Application Binary Interface)。
- 資料型別:Java虛擬機器棧主要處理Java型別的值,而本地方法棧可能涉及更廣泛的C/C++型別或其他原生型別。
2.3 本地方法棧的工作流程
- 方法呼叫:當Java程式碼中呼叫一個宣告為
native
的方法時,JVM會查詢該方法的本地實現。 - 棧幀建立:JVM為本地方法建立一個新的棧幀,並將其壓入本地方法棧。
- 引數傳遞:呼叫本地方法所需的引數會被從Java虛擬機器棧複製到本地方法棧的棧幀中。
- 方法執行:本地方法在本地方法棧中執行,可以訪問作業系統資源、硬體裝置等。
- 結果返回:本地方法執行完畢後,結果會被從本地方法棧複製回Java虛擬機器棧,然後繼續執行Java程式碼。
- 棧幀彈出:本地方法棧中的棧幀被彈出並丟棄。
2.4 總結
本地方法棧是JVM中用於支援本地方法呼叫的重要資料結構。透過本地方法棧,Java程式可以呼叫用其他語言編寫的程式碼,從而實現更廣泛的功能和更高的效能。