第41篇-JNIEnv與JavaVM的初始化


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是JNIE​nv_的別名。​

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,拉你入虛擬機群交流
 

 

 

  

  

  

  

 

 

 

 

 

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM