前言:我們知道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

