JNI 源碼分析

前言:我們知道cpu只認得“0101101”類似這種符號,C、C++這些代碼最終都得通過編譯、匯編成二進制代碼,cpu才能識別。而Java比C、C++又多了一層虛擬機,過程也復(fù)雜許多。Java代碼經(jīng)過編譯成class文件、虛擬機裝載等步驟最終在虛擬機中執(zhí)行。class文件里面就是一個結(jié)構(gòu)復(fù)雜的表,而最終告訴虛擬機怎么執(zhí)行的就靠里面的字節(jié)碼說明。

Java虛擬機在執(zhí)行的時候,可以采用解釋執(zhí)行和編譯執(zhí)行的方式執(zhí)行,但最終都是轉(zhuǎn)化為機器碼執(zhí)行。Java虛擬機運行時的數(shù)據(jù)區(qū),包括方法區(qū)、虛擬機棧、堆、程序計數(shù)器、本地方法棧。

問題來了,按我們目前的理解,如果是解釋執(zhí)行,那么方法區(qū)中應(yīng)該存的是字節(jié)碼,那執(zhí)行的時候,通過JNI動態(tài)裝載的c、c++庫,怎么加載進來的?

1. javac 與javap 處理 native方法ACC_NATIVE可見,普通的“add”方法是直接把字節(jié)碼放到code屬性表中,而native方法,與普通的方法通過一個標(biāo)志“ACC_NATIVE”區(qū)分開來。java在執(zhí)行普通的方法調(diào)用的時候,可以通過找方法表,再找到相應(yīng)的code屬性表,最終解釋執(zhí)行代碼,那么,對于native方法,在class文件中,并沒有體現(xiàn)native代碼在哪里,只有一個“ACC_NATIVE”的標(biāo)識,那么在執(zhí)行的時候改怎么找到動態(tài)鏈接庫的代碼呢?

一. 找到so庫文件

? ? ? ? ? ? ? ? ? 兩種方案:

? ? ? ? ? ? ? ? ? ? ? ? ?1. ClassLoader通過打印,知道實際的ClassLoder是PathClassLoader

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?----->findLibrary

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?---->this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ---->DexPathList.java

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?---->findLibrary

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ---->分析構(gòu)造函數(shù)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?解釋路徑

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2. ClassLoader為空----->getLibPaths() ----->System.mapLibraryName //根據(jù)庫類型 添加libxxx.so 還是xxx.dll


? ?二. 加載so庫文件

? ? ? ? ? ? ? ? ? ? ? ?----->Runtime.java ---->doLoad(filename, loader);

? ? ? ? ? ? ? ? ? ? ? ? ----->//libcore/ojluni/src/main/native/Runtime.c

? ? ? ? ? ? ? ? ? ? ? ? --->Runtime_nativeLoad預(yù)留動態(tài)注冊:jniRegisterNativeMethods? ? ? ? JVM_NativeLoad

? ? ? ? ? ? ? ? ? ? ? ? ?---->//art/runtime/openjdkjvm/OpenjdkJvm.cc

? ? ? ? ? ? ? ? ? ? ?---->LoadNativeLibrary()-

? ? ? ? ? ? ?--->sym = library->FindSymbol("JNI_OnLoad", nullptr);//在我們要加載so庫中查找JNI_OnLoad方法,如果沒有系統(tǒng)就認為是靜態(tài)注冊方式進行的,直接返回true,代表so庫加載成功,如果找到JNI_OnLoad就會調(diào)用JNI_OnLoad方法,JNI_OnLoad方法中一般存放的是方法注冊的函數(shù),所以如果采用動態(tài)注冊就必須要實現(xiàn)JNI_OnLoad方法,否則調(diào)用java中申明的native方法時會拋出異常,下面有JNI_OnLoad的實現(xiàn)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?----->JNI_OnLoadFn jni_on_load = reinterpret_cast(sym);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ----->IsBadJniVersion(version)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?------>version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;



靜態(tài)注冊:

每個class都需要使用javah生成一個頭文件,并且生成的名字很長書寫不便;初次調(diào)用時需要依據(jù)名字搜索對應(yīng)的JNI層函數(shù)來建立關(guān)聯(lián)關(guān)系,會影響運行效率

用javah 生成頭文件方便簡單

動態(tài)注冊:

使用一種數(shù)據(jù)結(jié)構(gòu)JNINativeMethod來記錄java native函數(shù)和JNI函數(shù)的對應(yīng)關(guān)系

移植方便(一個java文件中有多個native方法,java文件的包名更換后)


synchronized void loadLibrary0(ClassLoader loader, String libname) {

if (libname.indexOf((int)File.separatorChar) != -1) {

throw new UnsatisfiedLinkError(

"Directory separator should not appear in library name: " + libname);

}//判斷傳入的庫名稱是否合法,比如我們的庫是libxxx.so,我們只需要傳入xxx就可以了

String libraryName = libname;

if (loader != null) {//如果類加載器不為空

String filename = loader.findLibrary(libraryName);//查找是否存在我們需要的庫文件

if (filename == null) {

// It's not necessarily true that the ClassLoader used

// System.mapLibraryName, but the default setup does, and it's

// misleading to say we didn't find "libMyLibrary.so" when we

// actually searched for "liblibMyLibrary.so.so".

throw new UnsatisfiedLinkError(loader + " couldn't find \"" +

System.mapLibraryName(libraryName) + "\"");

}//不存在庫文件則拋出異常

String error = doLoad(filename, loader);//如果庫文件存在,就加載

if (error != null) {

throw new UnsatisfiedLinkError(error);

}//加載庫文件失敗,拋出異常

return;

}


JNIEXPORT jstring JNICALL

Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,

jobject javaLoader, jstring javaLibrarySearchPath)

{

return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);//調(diào)用JVM_NativeLoad方法

}//nativeLoad方法在本地的實現(xiàn)

void register_java_lang_Runtime(JNIEnv* env) {

jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));

}//注冊gMethods中的方法,這個函數(shù)是直接由虛擬機調(diào)用

typedef struct {

const char* name; /*Java 中函數(shù)的名字*/

const char* signature; /*描述了函數(shù)的參數(shù)和返回值*/

void* fnPtr; /*函數(shù)指針,指向 C 函數(shù)*/

} JNINativeMethod;


附件:

JNI



JNI原理

注意:Android? jni目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容