HotSpot 源碼閱讀 - JavaMain方法(2)


經過上一節我們知道最終main方法中會通過pthread_create創建一個新的線程來執行JavaMain方法,下面我們慢慢來剖析JavaMain核心流程,在此列出來的代碼我們也只列出來核心代碼,後續就不再做此說明

JavaMain

java.c 文件中JavaMain方法中主要初始化Jvm虛擬機,加載Java程序需要使用到的mainClass, 找到main方法,執行main,這樣我們Java main就成功的被調用了

int JNICALL JavaMain(void * _args)
{
	/* Initialize the virtual machine */
	if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    //Get the application's main class. It also checks if the main method exists.
    jclass mainClass = LoadMainClass(env, mode, what);
	/*
     * The LoadMainClass not only loads the main class, it will also ensure
     * that the main method's signature is correct, therefore further checking
     * is not required. The main method is invoked here so that extraneous java
     * stacks are not in the application stack trace.
     */
    jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    /* Invoke main method. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
}

執行流程說明:

  1. InitializeJVM初始化Jvm,這個方法內部邏輯比較複雜先不要去挖,看完主流程,它的功能就是初始化Java虛擬機,包含如下模塊,內存管理,GC,類加載模塊等
  2. LoadMainClass ,獲取應用程序的Main Class,也就是帶main方法class ,jclass聲明下面列出來了,是一個空無成員變量的class指針,由此可知這個指針肯定就是指向了一個class在內存中的地址
class _jobject {};
class _jclass : public _jobject {}; 
typedef _jclass *jclass;   
  1. GetStaticMethodID ,通過這個能獲取到mainClass的main方法在內存中的地址,返回值爲jmethodID 指向一個空結構體的指針,這個指針是指向了方法體在內存中的地址
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
  1. CallStaticVoidMethod ,調用查找到的main方法,這樣就進入了我們使用java寫的main方法了,程序開始執行我們寫Java main方法了,在此我們注意到最後兩方法都是調用JNIEnv *env的方法,此結構定義在Jni.h文件中 ,此接口定義了很多函數指向,而這些是在InitializeJVM方法進行賦值,我們看下定義;再往下看看,此結構體是怎麼賦值,裏面的這些方法在什麼文件中定義,方便後面深入具體方法
typedef const struct JNINativeInterface_ *JNIEnv;
struct JNINativeInterface_  {
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID (JNICALL *GetMethodID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
jobject (JNICALL *CallStaticObjectMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
}

InitializeJVM

InitializeJVM 將JavaVM **pvm, JNIEnv **penv繼續往傳遞調用ifn->CreateJavaVM,上節我們知道ifn->CreateJavaVM就是指向JNI_CreateJavaVM函數

/*
 * Initializes the Java Virtual Machine. Also frees options array when finished. 
 */
static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
    JavaVMInitArgs args;
    jint r;
    memset(&args, 0, sizeof(args));
    args.version  = JNI_VERSION_1_2;
    args.nOptions = numOptions;
    args.options  = options;
    args.ignoreUnrecognized = JNI_FALSE;
    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    return r == JNI_OK;
}

###JNI_CreateJavaVM
在jni.cpp中,會直接調用JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args),此方法有些長我們暫時先關注JNIEnv如何賦值,後續在解密其他方法

static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
	result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
    if (result == JNI_OK) {
	    JavaThread *thread = JavaThread::current();
	    assert(!thread->has_pending_exception(), "should have returned not OK");
	    /* thread is thread_in_vm here */
	    *vm = (JavaVM *)(&main_vm);
	    *(JNIEnv**)penv = thread->jni_environment();
	}
}

由如下代碼可知JNIEnv* penv = thread->jni_environment(),那麼我們從這可以知道了這個JNIEnv是當前創建的線程的一個成員變量_jni_environment,我們可以繼續看下這個變量如何賦值的 ;

如下代碼賦值代碼都在thread.cpp中

thread->jni_environment();

  // Returns the jni environment for this thread
  JNIEnv* jni_environment()                      { return &_jni_environment; }
  

_jni_environment 又是線程的一個成員變量,所以我跟蹤到賦值的方法set_jni_functions

//JNI functiontable getter/setter for JVMTI jni function table interception API.
  void set_jni_functions(struct JNINativeInterface_* functionTable) {
    _jni_environment.functions = functionTable;
  }

set_jni_functions此方法在線程的初始化流程進行調用,而線程創建是在Threads::create_vm中

Threads::create_vm

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain)
{
	// Attach the main thread to this os thread
   JavaThread* main_thread = new JavaThread();
}	

JavaThread::JavaThread(bool is_attaching_via_jni) :Thread()
{
	initialize();
}                    
// A JavaThread is a normal Java thread
void JavaThread::initialize() {
  	// Initialize fields
	set_saved_exception_pc(NULL);
  	set_threadObj(NULL);
 	 _anchor.clear();
  	set_entry_point(NULL);
  	set_jni_functions(jni_functions());  //這個就是在線程的構造方法中調用
}

在此我們先總結下調用流 ,JavaThread構造方法 -> JavaThread::initialize -> JavaThread::set_jni_functions ,將jni_functions()返回值賦值給了JNIEnv* penv

jni_functions()

此函數在jni.cpp中

// Returns the function structure
struct JNINativeInterface_* jni_functions() {
  return &jni_NativeInterface;
}

再看先jni_NativeInterface的定義,就是一對函數地址組成的結構體,這些函數都是以jni開頭,並且就定義在此文件中,此時我們回頭再看 (*env)->GetStaticMethodID 的定義就在這

// Structure containing all jni functions
struct JNINativeInterface_ jni_NativeInterface = {
	//....
	jni_GetStaticMethodID,
	jni_CallStaticObjectMethod
	//.... 
}

到此我們知道JavaMain 方法中,首先初始化JVM,初始jvm的時候,創建對應的線程,對JniEnv進行了賦值,JVM初始化流程比較多,我們先暫時放放, 然後就是去加載Main Class, 然後通過JniEnv的方法GetStaticMethodID獲取Main方法,然後通過JniEnv方法CallStaticObjectMethod調用Java main方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章