Java jvm級別native關鍵詞、JNI詳解

java鯊魚發表於2020-12-18

1.native關鍵詞的引入

再完美的程式語言也有自己的不足之處,當然Java也不例外,Java的不足之處除了體現在執行速度(這點往往被一些其他程式語言使用者所詬病)上要比傳統的C++慢許多之外,Java無法直接訪問到作業系統底層(如系統硬體等),為此Java使用native(原生的)關鍵詞來擴充套件Java程式的功能。native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前檔案,而是在用其他語言(如C和C++)實現的檔案中。Java語言本身不能對作業系統底層進行訪問和操作,但是可以通過JNI介面呼叫其他語言來實現對底層的訪問。JNI是Java Native Interface的縮寫,它提供了若干的API實現了Java和其他語言的通訊(主要C&C++)。從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java程式碼和其他語言寫的程式碼進行互動。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他程式語言,只要呼叫約定受支援就可以了。使用java與本地已編譯的程式碼互動,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的。例如,使用一些舊的庫,與硬體、作業系統進行互動,或者為了提高程式的效能。JNI標準至少要保證原生程式碼能工作在任何Java 虛擬環境。簡而言之,JNI功能即為:Java和原生程式碼間的雙向互動。JNI允許Java程式碼使用以其他語言編寫的程式碼和程式碼庫。Invocation API(JNI的一部分)可以用來將Java虛擬機器(JVM)嵌入到本機應用程式中,從而允許程式設計師從本機程式碼內部呼叫Java程式碼。

2.JNI的使用場合

1、JAVA程式和本地程式使用TCP/IP或者IPC進行互動。
2、當用JAVA程式連線本地資料庫時,使用JDBC提供的API。
3、JAVA程式可以使用分散式物件技術,如JAVA IDL API。
這些方案的共同點是,JAVA和C處於不同的執行緒,或者不同的機器上。這樣,當本地程式崩潰時,不會影響到JAVA程式。
下面這些場合中,同一程式內JNI的使用無法避免:
1、程式當中用到了JAVA API不提供的特殊系統環境才會有的特徵。而跨程式操作又不現實。
2、可能想訪問一些己有的本地庫,但又不想付出跨程式呼叫時的代價,如效率,記憶體,資料傳遞方面。
3、JAVA程式當中的一部分程式碼對效率要求非常高,如演算法計算,圖形渲染等。
總之,只有當必須在同一程式中呼叫原生程式碼時,再使用JNI。

3.JNI的書寫步驟

第一步:編寫帶有native宣告的方法的Java類

第二步:使用javac命令編譯編寫的Java類

第三步:使用java -jni 來生成字尾名為.h的標頭檔案

第四步:使用其他語言(C、C++)實現本地方法

第五步:將本地方法編寫的檔案生成動態連結庫

第六步:執行程式

4.一個JNI的簡單應用例子(以HelloJNI為例子)

第一步:編寫帶有native宣告的方法的Java類public class HelloJNI {

   public native void displayHelloJNI();//所有native關鍵詞修飾的都是對本地的宣告
    static {
        System.loadLibrary("hello");//載入本地庫
    }
    public static void main(String[] args) {
        new JNI().displayHelloJNI();
    }
}

宣告native方法:如果想將一個方法做為一個本地方法的話,那麼就必須宣告該方法為native,並且不能實現。 Load動態庫:System.loadLibrary(“hello”);載入動態庫(可以這樣理解:方法 displayHelloJNI()沒有實現,但是在後續步驟中就直接使用了,所以必須在使用之前對它進行初始化),這裡一般是以static塊進行載入的。同時需要注意的是System.loadLibrary()的引數“hello”是動態庫的名字。

第二步:使用javac命令編譯編寫的Java類

javac HelloJNI.java

注意:JDK環境變數的配置

第三步:使用java -jni 來生成字尾名為.h的標頭檔案( 生成副檔名為h的標頭檔案javah HelloJNI)

標頭檔案javah HelloJNI內容如下:

/*DO NOT EDI TTHIS FILE - it is mach inegenerated*/
#include<jni.h>
/*Header for class HelloJNI*/

#ifndef_Included_HelloJNI
#define_Included_HelloJNI
#ifdef__cplusplus
extern"C"{
#endif
/*
*Class:HelloJNI
*Method:displayHelloJNI
*Signature:()V
*/
JNIEXPORTvoidJNICALL
Java_HelloJNI_displayHelloJNI(JNIEnv*,jobject);

#ifdef__cplusplus
}
#endif
#endif

注意:這個h檔案相當於在java裡面的介面,這裡宣告瞭一個Java_HelloJNI_displayHelloJNI (JNIEnv *, jobject)方法,然後在本地方法裡面實現這個方法,也就是說在編寫C/C++程式的時候所使用的方法名必須和這裡的一致

第四步:使用其他語言(C、C++)實現本地方法(編寫本地方法實現和由javah命令生成的標頭檔案裡面宣告的方法名相同的方法)

#include"jni.h"
#include"HelloJNI.h"

//#includeotherheaders

JNIEXPORT void JNICALL
Java_HelloJNI_displayHelloJNI(JNIEnv*env,jobject obj)
{
printf("HelloJNI!\n");
return;
}

注意:程式碼第一行需要將jni.h(該檔案可以在%JAVA_HOME%/include資料夾下面找到)檔案引入,因為在程式中的JNIEnv、 jobject等型別都是在該標頭檔案中定義的;另外在第2行需要將HelloJNI.h標頭檔案引入,然後儲存為 HelloJNIImpl.c。

第五步:將本地方法編寫的檔案生成動態連結庫

這裡以在Windows中為例,需要生成dll檔案。在儲存HelloWorldJNI.c資料夾下面,使用VC的編輯器cl為:cl -I%java_home%\include -I%java_home%\include\win32 -LD HelloJNIImp.c -Fehello.dll 注意:生成的dll檔名在選項-Fe後面配置,這裡是hello,因為在HelloJNI.java檔案中我們loadLibary的時候使用的名字是hello。當然這裡修改之後那裡也需要修改。另外需要將-I%java_home%\include -I%java_home%\include\win32引數加上,因為在第四步裡面編寫本地方法的時候引入了jni.h檔案。

如果配置了MinGW,也可以這樣來編譯:gcc -Wall -D_JNI_IMPLEMENTATION_ -Wl,–kill-at -Id:/java/include –Id:/java/include/win32 -shared -o (輸出的dll檔名,如sum.dll) (輸入的c/c++原始檔,如abc.c)。

第六步:執行程式

執行javaHelloJNI;如果用eclipse/myeclipse,需將dll或so檔案放在專案下,而不是src及其子目錄下;如果用命令列編譯,把dll檔案放在該包的同目錄下。

image

最新2020整理收集的一些高頻面試題(都整理成文件),有很多幹貨,包含mysql,netty,spring,執行緒,spring cloud、jvm、原始碼、演算法等詳細講解,也有詳細的學習規劃圖,面試題整理等,
需要獲取這些內容的朋友請加Q君樣:
756584822

相關文章