JavaVM和JNIEnv的初始化和JVM各模塊的初始化都是在JNI_CreateJavaVM()函數中完成。這一篇將詳細介紹JavaVM和JNIEnv的初始化過程。
1、初始化JavaVM
JavaVM的初始化都是在JNI_CreateJavaVM()函數中完成,調用鏈如下:
JavaMain() java.c InitializeJVM() java.c JNI_CreateJavaVM() jni.cpp
在JavaMain()函數中調用InitializeJVM()函數,InitializeJVM()函數會初始化JVM,給JavaVM和JNIEnv變量賦值,通過InvocationFunctions結構體下的CreateJavaVM函數指針來調用對應的函數。在執行LoadJavaVM()函數時,CreateJavaVM函數指針被指向為libjvm.so(在Linux系統上的動態鏈接庫為libjvm.so)動態鏈接庫中JNI_CreateJavaVM()函數。JNI_CreateJavaVM()函數的實現如下:
源代碼位置:openjdk/hotspot/src/share/vm/jni.cpp _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) { // ... jint result = JNI_ERR; bool can_try_again = true; //完成JVM的初始化,如果初始化過程中出現不可恢復的異常則can_try_again會被置為false result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again); //初始化正常 if (result == JNI_OK) { //獲取當前線程,即執行create_vm()函數的線程,也是JNI_CreateJavaVM()函數執行完畢后執行main()方法的線程 JavaThread *thread = JavaThread::current(); // JavaVM賦值,main_vm是jni.h中定義的全局變量,最終會指向全局變量jni_InvokeInterface *vm = (JavaVM *)(&main_vm); // vm是方法的參數,是個雙重指針 //JNIEnv賦值,從這里也可以看出,JNIEnv其實是線程私有變量 *(JNIEnv**)penv = thread->jni_environment(); // ... } // ... return result; }
調用Threads::create_vm()函數會初始化一系列的JVM模塊,如果函數順利執行完,則函數會返回JNI_OK,表示正確創建出了JVM實例,我們可以給vm賦值,這樣JNI的本地函數就可以通過vm來管理這個創建出來的JVM實例了。
使用main_vm來初始化JavaVM類型的變量,main_vm變量的定義如下:
源代碼來源:openjdk/hotspot/src/share/vm/jni.cpp struct JavaVM_ main_vm = { &jni_InvokeInterface };
JavaVM是JavaVM_的別名,所以我們后面提到的JavaVM指的就是JavaVM_。同理,JNIEnv是JNIEnv_的別名。
jni_InvokeInterface的定義如下:
const struct JNIInvokeInterface_ jni_InvokeInterface = { NULL, NULL, NULL, jni_DestroyJavaVM, jni_AttachCurrentThread, jni_DetachCurrentThread, jni_GetEnv, jni_AttachCurrentThreadAsDaemon };
結構體中保存了一系列函數指針,我們前面說的JNI的本地函數通過JavaVM來管理JVM實例,其實就是通過函數指針來調用對應的函數管理JVM實例。
通過 JavaVM,我們還可以獲得 JVMTI 的指針,並獲得 JVMTI 函數的使用能力,所有的 JVMTI 函數都通過jvmtiEnv獲取,不同的虛擬機實現提供的函數細節可能不一樣,但是使用的方式是統一的,關於JVMTI及相關知識這里不介紹,有興趣的可自行研究。
2、初始化JNIEnv
JavaMain() java.c InitializeJVM() java.c JNI_CreateJavaVM() jni.cpp Threads::create_vm() thread.cpp JavaThread::JavaThread() thread.cpp JavaThread::initialize() thread.cpp jni_functions() jni.cpp
其中調用的Threads:create_vm()函數中會創建JavaThread實例,如下:
JavaThread* main_thread = new JavaThread()
根據調用鏈可知,在JavaThread構造函數中會調用JavaThread::initialize()函數,最終會調用jni_functions()函數。jni_functions()函數會返回thread.cpp文件中定義的一個全局變量,如下:
struct JNINativeInterface_* jni_functions() { return &jni_NativeInterface; }
jni_NativeInterface這個全局變量的定義如下:
struct JNINativeInterface_ jni_NativeInterface = { NULL,f NULL, NULL, NULL, jni_GetVersion, jni_DefineClass, jni_FindClass, ... };
如上的結構體中保存了各個JNI函數的指針。
調用jni_functions()函數后還會調用如下函數:
void set_jni_functions(struct JNINativeInterface_* functionTable) { _jni_environment.functions = functionTable; }
其中_jni_environment變量的定義如下:
源代碼位置:openjdk/hotspot/src/share/vm/rumtime/thread.hpp // 定義在JavaThread中的變量,所以JNIEnv是線程私有的 JNIEnv _jni_environment;
JNIEnv這個結構體定義在之前說過,在這個結構體中首先定義的就是一個指向JNINativeInterface_的指針functions,如下:
struct JNIEnv_ { const struct JNINativeInterface_ *functions; ... }
_jni_environment就是main_thread保留的JNIEnv_實例了,正確設置functions屬性就表示該實例初始化完成。
為了讓大家一目了然,我簡單畫了一個示意圖。
我們將創建出來的JNIEnv實例保存在線程的_jni_environment變量中,這樣當線程執行本地函數時,會從這個變量中取出JNIEnv並傳遞給本地函數,我們得到JNIEnv后就能調用其中的函數來獲取JVM內部提供的服務了,例如想要根據類名來查找jclass,可以調用FindClass()函數。
公眾號 深入剖析Java虛擬機HotSpot 已經更新虛擬機源代碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛擬機群交流