日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM源码阅读-本地库加载流程和原理

發布時間:2025/3/15 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM源码阅读-本地库加载流程和原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

本文主要研究OpenJDK中JVM源碼中涉及到native本地庫的加載流程和原理的部分。主要目的是為了了解本地庫是如何被加載到虛擬機,以及是如何找到并執行本地庫里的本地方法,以及JNI的?JNI_OnLoad?和?JNI_OnUnLoad是何時被調用的 。?

1.載入本地庫

使用JNI的第一步,往往是在Java代碼里面加載本地庫的so文件,例如:

public class Test {static {System.loadLibrary("my_native_library_name");} }

那么我們從這個方法作為入口來研究JDK的代碼。?

2. 尋找本地庫文件

System.java

源碼在 OpenJdk/jdk/src/share/classes/java/lang/System.java

? public static void loadLibrary(String libname) {Runtime.getRuntime().loadLibrary0(getCallerClass(), libname);}?

Runtime.java

源碼在 OpenJdk/jdk/src/share/classes/java/lang/Runtime.java

然后來看?java.lang.Runtime?類時如何來 loadLibrary 的:

? synchronized void loadLibrary0(Class fromClass, String libname) {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkLink(libname);}if (libname.indexOf((int)File.separatorChar) != -1) {throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);}ClassLoader.loadLibrary(fromClass, libname, false);}

它首先做了一些安全性檢查,然后使用?ClassLoader?來載入本地庫的。?

ClassLoader.java

源碼在 OpenJdk/jdk/src/share/classes/java/lang/ClassLoader.java

接下來看?ClassLoader?是如何實現的具體的加載工作的:

首先根據?libname?參數找到本地庫的文件路徑,并訪問該so庫文件來載入,

在這里會在幾個地方去找so庫文件:

  • Class Loader找到的絕對路徑(class path)
  • java.library.path?定義的目錄下(windows下的PATH,linux下的LD_LIBRARY_PATH)
  • sun.boot.library.path?定義的目錄下
  • usr_paths = initializePath("java.library.path"); sys_paths = initializePath("sun.boot.library.path"); // 首先從class loader的目錄讀取 String libfilename = loader.findLibrary(name); // ... File libfile = new File(libfilename); // ... if (loadLibrary0(fromClass, libfile)) {return; } // 然后嘗試從sys_paths目錄下讀取so文件 for (int i = 0 ; i < sys_paths.length ; i++) {File libfile = new File(sys_paths[i], System.mapLibraryName(name));if (loadLibrary0(fromClass, libfile)) {return;} } // 最后嘗試從usr_paths目錄下讀取so文件 for (int i = 0 ; i < usr_paths.length ; i++) {File libfile = new File(usr_paths[i], System.mapLibraryName(name));if (loadLibrary0(fromClass, libfile)) {return;} }

    其中可以看到,對于傳入給?System.loadLibrary(String libname)?的參數?libname?是通過調用?System.mapLibraryName?方法來將其映射為庫文件的文件名。

    這個方法是一個native方法,不同系統有不同的實現,具體的區別主要在于前綴和擴展名的不同,例如在 linux 平臺下前綴和擴展名分為定義為:

    #define JNI_LIB_PREFIX "lib" #define JNI_LIB_SUFFIX ".so"?

    3. 維護本地庫列表

    對于找到so庫文件以后,具體的加載工作是由?loadLibrary0?方法來完成的,

    首先如果有?ClassLoader?則將本地庫加載到該?ClassLoader?的本地庫列表中,如果沒有則加載到系統本地庫列表中。

    ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); Vector<NativeLibrary> libs = ? ? ? ? ? loader != null ? loader.nativeLibraries : systemNativeLibraries;

    然后遍歷已經加載的本地庫列表,如果發現這個本地庫已經被system或這個classLoader加載過了,則不再執行加載工作,直接返回true。這里也防止了我們重復的去調用?System.loadLibrary?去加載同一個庫。

    這里需要注意的是一個本地庫只能被同一個?ClassLoader?(或線程)加載,一旦被某個?ClassLoader?(或線程)加載過了,再使用另一個?ClassLoader?(或線程)去加載它,則會拋出異常。

    然后的本地庫都會被封裝成?NativeLibrary?對象,并存入 ClassLoader 的靜態Stack里面。然后調用它的?load?方法來完成加載功能。

    ?

    這里需要先了解一下,本地庫被誰加載,加載以后存在哪里:

    首先系統類去維護一個本地庫列表,其中保存了由系統加載的本地庫名稱。

    // Native libraries belonging to system classes. private static Vector<NativeLibrary> systemNativeLibraries = new Vector<>();

    然后每個 ClassLoader 實例都必須去維護一個列表,其中保存了所有由它加載過的本地庫名稱。

    // Native libraries associated with the class loader. private Vector<NativeLibrary> nativeLibraries = new Vector<>();

    最有所有的被加載過的本地庫名稱列表,以靜態變量的形式保存起來。

    // All native library names we've loaded. private static Vector<String> loadedLibraryNames = new Vector<>();

    然后所有的本地庫在加載后,都被以?NativeLibrary?類型保存在 ClassLoader 的靜態Stack里。?

    NativeLibrary

    源碼在 OpenJdk/jdk/src/share/classes/java/lang/ClassLoader.java

    NativeLibrary是ClassLoader的靜態內部類,用于封裝已經加載過的本地庫信息。每個NativeLibrary對象都需要有一個JNI的版本號。這個版本號是虛擬機在載入本地庫的時候獲取并設置的。

    它有主要的三個方法,并且它們都是native方法,依次是:

    native void load(String name); native long find(String name); native void unload();

    load?方法用于加載本地庫。

    find?方法用于找到本地庫的指針地址。

    unload?方法用于卸載本地庫。

    ?

    另外在其?finalize?方法里,將其從 ClassLoader 中保存的已加載本地庫列表中移除。

    ?

    4. 加載和卸載本地庫

    ClassLoader.c

    源碼在:OpenJDK/jdk/src/share/native/java/lang/ClassLoader.c

    在此主要關注java層的NativeLibrary類其中的三個native方法,來了解具體是如何加載和卸載本地庫的。

    ?

    NativeLibrary_load

    首先來看本地代碼是如何加載一個本地庫的。

    JNIEXPORT void JNICALL Java_java_lang_ClassLoader_00024NativeLibrary_load(JNIEnv *env, jobject this, jstring name)

    注意:這里的?_00024?表示的是?$?符號,用來在java中表示內部類。

    這里需要說明的是最后一個參數?name?,它是在構建一個?NativeLibrary?對象時傳進來的,是本地庫文件的完整路徑,其是調用 Java 中的?File.getCanonicalPath()?方法來獲取的。?

    Step 1: 先加載本地庫文件

    其中最關鍵的在于根據傳入的這個 name (會將jstring類型轉換成char*類型),來加載本地庫:

    handle = JVM_LoadLibrary(cname);?

    Step 2: 再執行JNI_OnLoad函數

    在其加載成功后,會去尋找?JNI_OnLoad?函數,并執行它,?JNI_OnLoad?函數返回的其使用的JNI版本號的值,如果沒有找到該方法,則默認使用 JNI 1_1 作為版本號。

    如果返回的是一個不支持的版本號,則會拋出?UnsatisfiedLinkError?異常。

    ?

    其中?JVM_LoadLibrary?函數定義為:

    OpenJdk/jdk/src/share/javavm/export 目錄下的?jvm.h?文件中:

    JNIEXPORT void * JNICALL JVM_LoadLibrary(const char *name);

    具體的實現由虛擬機在實現,例如 hotspot 的實現在

    OpenJdk/hotspot/src/share/vm/prims 目錄下的?jvm.cpp?文件:

    JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name))//%note jvm_ct JVMWrapper2("JVM_LoadLibrary (%s)", name);char ebuf[1024];void *load_result;{ThreadToNativeFromVM ttnfvm(thread);load_result = os::dll_load(name, ebuf, sizeof ebuf);}if (load_result == NULL) {char msg[1024];jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf);// Since 'ebuf' may contain a string encoded using ? // platform encoding scheme, we need to pass ? // Exceptions::unsafe_to_utf8 to the new_exception method ? // as the last argument. See bug 6367357. ? Handle h_exception = ? ? Exceptions::new_exception(thread,vmSymbols::java_lang_UnsatisfiedLinkError(),msg, Exceptions::unsafe_to_utf8);THROW_HANDLE_0(h_exception);}return load_result; JVM_END

    其中能看到重要的在于?os::dll_load?函數,它是根據系統不同而由不同的實現的。

    linux實現

    例如在 linux 系統下的實現在?openjdk/hotspot/src/os/linux/vm/os_linux.cpp?文件中。

    它其中主要做了兩件事情,一個是使用 linux 的?dlopen?來打開這個so本地庫文件,再則檢查了這個so本地庫文件是否和當前運行虛擬機的CPU架構是否相同。

    dlopen函數定義在?dlfcn.h,原型為:

    void * dlopen( const char * pathname, int mode);

    其中第二個參數使用的是?RTLD_LAZY: 異常綁定。

    windows實現

    windows的實現是使用?LoadLibrary?函數來加載 dll 本地庫。?

    NativeLibrary_unload

    Step1: 先執行JNI_OnUnLoad方法

    虛擬機在卸載本地庫文件之前,會先回調本地庫文件中的?JNI_OnUnLoad?函數,可以在該函數中執行一些清理工作,例如清理全局變量等。?

    Step2: 再卸載本地庫文件

    JVM_UnloadLibrary?和?`JVM_loadLibrary?函數一樣,具體根據平臺不同而實現:

    在linux平臺上,使用?dlopen?函數來 load so文件, 使用?dlclose?函數來 unload.

    在windows平臺上,使用?LoadLibrary?函數來load dll文件,來?FreeLibrary?函數來 unload.?

    NativeLibrary_find

    尋找本地庫里的某個方法或全局變量的內存地址。

    在不同平臺上的實現不一樣:

    在linux平臺上,使用dlsym?函數來獲取某個方法的內存地址。

    在windows平臺上,使用?GetProcAddress?函數來獲取某個方法的內存地址。?

    ?注意:在?NativeLibrary_load?和?NativeLibrary_unload?兩個函數內,不是調用了so庫里面的?JNI_OnLoad?和?JNI_OnUnLoad?函數嘛,其就是使用?NativeLibrary_find?函數來找到這兩個函數地址,并執行它們了。

    handle = jlong_to_ptr((*env)->GetLongField(env, this, handleID)); JNI_OnUnload = (JNI_OnUnload_t )JVM_FindLibraryEntry(handle, onUnloadSymbols[i]); ?if (JNI_OnUnload) {JavaVM *jvm;(*env)->GetJavaVM(env, &jvm);(*JNI_OnUnload)(jvm, NULL); }

    這里面有一個比較重要的變量就是?handleID?,這個handleID是從哪里來,存在哪里都比較關鍵。

    首先我們來看這個handleID來至哪里,它其實是?JVM_LoadLibrary?返回的值,即?dlopen?返回的值,這個比較簡單,它是在打開本地庫時返回的句柄,然后這個句柄并沒有保存在native層,而是將其保存在了Java層。

    在調用?NativeLibrary_load?函數里,將這個?handleID?保存到了這個?NativeLibrary?Java對象的?long handle成員域里。每次需要使用?handleID?的時候都從這個Java對象里面的成員域去取。?

    5. 加載流程小結

    從整個加載本地庫的流程來看,基本上還是調用和平臺有關的函數來完成的,并在加載和卸載的時候分別調用了兩個生命周期回調函數?JNI_OnLoad?和?JNI_OnUnLoad?。

    以linux平臺為例,簡單總結一下整個so庫的加載流程:

  • 首先?System.loadLibrary()?被調用,開始整個加載過程。
  • 其中調用?ClassLoader?對象來完成主要工作,將每個本地庫封裝成?NativeLibrary?對象,并以靜態變量存到已經加載過的棧中。
  • 執行NativeLibrary?類的?load?native方法,來交給native層去指向具體的加載工作。
  • native層?ClassLoader.c?中的?Java_java_lang_ClassLoader_00024NativeLibrary_load?函數被調用。
  • 在native load函數中首先使用?dlopen?來加載so本地庫文件,并將返回的handle保存到?NativeLibrary對象中。
  • 接著查找已經加載的so本地庫中的?JNI_OnLoad?函數,并執行它。
  • 整個so本地庫的加載流程完畢。?
  • 只有在?NativeLibrary?對象被GC回收的時候,其?finalize?方法被調用了,對應加載的本地庫才會被 unload 。這種情況一般來說并不會發生,因為?NativeLibrary?對象是以靜態變量的形式被保存的,而靜態變量是 GC roots,一般來說都不會被回收掉的。

    TODO: 那請問?JNI_OnUnLoad?函數什么情況下會被調用?虛擬機關閉的時候?一個本地庫被load后,是否能手動的unload?什么情況下才可能被unload??

    結語

    參考資料,JVM源碼:

    • OpenJdk/jdk/src/share/classes/java/lang/System.java
    • OpenJdk/jdk/src/share/classes/java/lang/Runtime.java
    • OpenJdk/jdk/src/share/classes/java/lang/ClassLoader.java
    • OpenJDK/jdk/src/share/native/java/lang/ClassLoader.c

    原文地址: http://blog.crasheye.cn/jvm-local-lib-loading-code-reading.html

    總結

    以上是生活随笔為你收集整理的JVM源码阅读-本地库加载流程和原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。