openjdk的啓動流程

從前文《openjdk1.8工程結構》我知道makefile如何編譯出我的java命令,同時也告知了程序入口main.c

main.c

作用:程序入口
位置:openjdk\jdk\src\share\bin\main.c

/*
 * Entry point.
 */
//java虛擬機啓動入口函數
#ifdef JAVAW   //window平臺入口
char **__initenv;
int WINAPI
WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_TRUE;

    __initenv = _environ;

#else /* JAVAW */   //linux平臺入口函數
int main(int argc, char **argv){
    int margc;   //參數個數
    char** margv;  //命令參數
    const jboolean const_javaw = JNI_FALSE;
#endif

#ifdef _WIN32
    {
        int i = 0;
        if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
            printf("Windows original main args:\n");
            for (i = 0 ; i < __argc ; i++) {
                printf("wwwd_args[%d] = %s\n", i, __argv[i]);
            }
        }
    }
    JLI_CmdToArgs(GetCommandLine());
    margc = JLI_GetStdArgc();
    // add one more to mark the end
    margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
    {
        int i = 0;
        StdArg *stdargs = JLI_GetStdArgs();
        for (i = 0 ; i < margc ; i++) {
            margv[i] = stdargs[i].arg;
        }
        margv[i] = NULL;
    }
#else /* *NIXES */
    margc = argc;
    margv = argv;
#endif /* WIN32 */

    //調用JLI_Launch執行java的執行體邏輯,參考代碼jdk8u-dev/jdk/src/share/bin/java.c
    return JLI_Launch(margc,  //命令參數個數
                   margv,     //參數數組
                   sizeof(const_jargs) / sizeof(char *), //java args參數個數 
                   const_jargs,  //java參數
                   sizeof(const_appclasspath) / sizeof(char *),  //classpath 數量
                   const_appclasspath, //classpath數量
                   FULL_VERSION, //完整的版本號
                   DOT_VERSION,  //版本號
                   (const_progname != NULL) ? const_progname : *margv, //程序名稱
                   (const_launcher != NULL) ? const_launcher : *margv, //啓動器名稱
                   (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,  //默認爲0
                   const_cpwildcard,  //是否支持擴展classpath  默認爲true
                   const_javaw,  //window下true, 其他false
                   const_ergo_class);//運行模式  默認爲DEFAULT_POLICY=0  其他包括NEVER_SERVER_CLASS、ALWAYS_SERVER_CLASS
}

查看代碼可知其核心啓動方法是JLI_Launch。

JLI_Launch

作用:java入口函數,解析參數、創建環境、加載jvm動態庫
位置:jdk8u-dev/jdk/src/share/bin/java.c

int
JLI_Launch(int argc, char ** argv,              /* main argc, argc */
        int jargc, const char** jargv,          /* java args */
        int appclassc, const char** appclassv,  /* app classpath */
        const char* fullversion,                /* full version defined */
        const char* dotversion,                 /* dot version defined */
        const char* pname,                      /* program name */
        const char* lname,                      /* launcher name */
        jboolean javaargs,                      /* JAVA_ARGS */
        jboolean cpwildcard,                    /* classpath wildcard*/
        jboolean javaw,                         /* windows-only javaw */
        jint ergo                               /* ergonomics class policy */
)
{
    int mode = LM_UNKNOWN; //啓動模式  默認爲0   其他參考:java.h 中LaunchMode枚舉定義
    char *what = NULL;
    char *cpath = 0;
    char *main_class = NULL;    //帶有main函數的class文件
    int ret;
    InvocationFunctions ifn;   //函數指針結構體包括:創建jvm、獲取jvm啓動參數、獲取創建的jvm
    jlong start, end;          //啓動結束時間
    char jvmpath[MAXPATHLEN];   //jvm路徑
    char jrepath[MAXPATHLEN];   //jre路徑
    char jvmcfg[MAXPATHLEN];    //jvm配置

    _fVersion = fullversion;
    _dVersion = dotversion;
    _launcher_name = lname;
    _program_name = pname;
    _is_java_args = javaargs;
    _wc_enabled = cpwildcard;
    _ergo_policy = ergo;

	//初始化啓動器,window下注冊啓動異常時彈出ui選擇調試器
    InitLauncher(javaw);
    DumpState(); //判斷是否定義環境變量_JAVA_LAUNCHER_DEBUG,是的話打印調試信息
    if (JLI_IsTraceLauncher()) {  //打印命令行參數
        int i;
        printf("Command line args:\n");
        for (i = 0; i < argc ; i++) {
            printf("argv[%d] = %s\n", i, argv[i]);
        }
        //增加配置參數
        AddOption("-Dsun.java.launcher.diag=true", NULL);
    }

   
    //選擇jre的版本,這個函數實現的功能比較簡單,就是選擇正確的jre版本來作爲即將運行java程序的版本。
    //選擇的方式
    //1.環境變量設置了_JAVA_VERSION_SET,那麼代表已經選擇了jre的版本,不再進行選擇
    //2.可以從jar包中讀取META-INF/MAINFEST.MF中獲取,查看mian_class
    //3.運行時給定的參數來搜索不同的目錄選擇jre_restrict_search
    //最終會解析出一個真正需要的jre版本並且判斷當前執行本java程序的jre版本是不是和這個版本一樣,如果不一樣調用linux的execv函數終止當前進出並且使用新的jre版本重新運行這個java程序,但是進程ID不會改變。
    SelectVersion(argc, argv, &main_class);

    //確定一下jvm的信息並且初始化相關信息,爲後面的jvm執行準備環境
    //1.首先查找jre路徑,通過GetApplicationHome來獲得
    //2.推斷JAVA_DLL文件位置,JVM.cfg文件
    //3.根據JVM.cfg獲取JVM_DLL位置,可以通過-XXaltJVM或者JDK_ALTERNATE_VM指定JVM_DLL
    CreateExecutionEnvironment(&argc, 
                               &argv,
                               jrepath, 
                               sizeof(jrepath),
                               jvmpath, 
                               sizeof(jvmpath),
                               jvmcfg,  
                               sizeof(jvmcfg));

    if (!IsJavaArgs()) {
    	//處理-XX:NativeMemoryTracking
    	//Hotspot VM用來分析VM內部內存使用情況的一個功能
        SetJvmEnvironment(argc,argv);
    }
    //初始設置函數指針爲無效指針
    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJavaVMInitArgs = 0;

    //如果時debug的話,記錄啓動時間
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }

    //動態加載JVM_DLL這個共享庫,把hotspot的接口爆露出來,例如JNI_CreateJavaVM函數
    //填充到ifn結構體中
    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }

    //debug模式下記錄加載java vm虛擬機時間
    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
    }

    //打印加載javavm時間
    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
             (long)(jint)Counter2Micros(end-start));

    ++argv;
    --argc;

    //是否有參數
    if (IsJavaArgs()) {
        /* 解析參數 */
        TranslateApplicationArgs(jargc, jargv, &argc, &argv);
        if (!AddApplicationOptions(appclassc, appclassv)) {
            return(1);
        }
    } else {
        /* 設置環境變量中的classpath路徑 */
        cpath = getenv("CLASSPATH");
        if (cpath == NULL) {
            cpath = ".";
        }
        SetClassPath(cpath);
    }

    /* 解析命令行參數,如果解析失敗則程序退出.
     */
    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) {
        return(ret);
    }

    /* 如果-jar參數指定的話,重寫classpath值 */
    if (mode == LM_JAR) {
        SetClassPath(what);     /* Override class path */
    }

    /* set the -Dsun.java.command pseudo property */
    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    SetJavaLauncherProp();

    /* set the -Dsun.java.launcher.* platform properties */
    SetJavaLauncherPlatformProps();

    //進行一系列處理,最終創建一個jvm的虛擬機並調用java運行入口函數
    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
  1. SelectVersion
     選擇正確的jre版本來作爲即將運行java程序的版本
     適用於一臺機子上要執行多個不同版本的java項目
  2. CreateExecutionEnvironment
     爲後面的jvm執行準備環境
     找到了JAVA_DLL,JVM_DLL
  3. LoadJavaVM
     動態加載JVM_DLL這個共享庫,從中獲取JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs三個函數的實現,其中JNI_CreateJavaVM是JVM初始化的核心入口,具體實現在hotspot目錄中
  4. ParseArguments
     解析命令行參數,如-version,-help等參數在該方法中解析的
  5. SetJavaCommandLineProp
      解析形如-Dsun.java.command=的命令行參數
  6. SetJavaLauncherPlatformProps
     解析形如-Dsun.java.launcher.*的命令行參數
  7. JVMInit
     進一步調用ContinueInNewThread

ContinueInNewThread

作用:組織參數,執行真正的虛擬機入口函數
位置:jdk8u-dev/jdk/src/share/bin/java.c

int
ContinueInNewThread(InvocationFunctions* ifn, //函數指針
                    jlong threadStackSize,    //線程棧大小
                    int argc, 
                    char **argv,
                    int mode, 
                    char *what, 
                    int ret)
{

    /*
     * 如果沒有指定線程棧大小, 則會檢查vm是否指定一個默認值.
     * 注意:虛擬機不再支持1.1,但是他會通過初始化參數返回一個默認的棧大小值.
     */
    if (threadStackSize == 0) {
      struct JDK1_1InitArgs args1_1;
      memset((void*)&args1_1, 0, sizeof(args1_1));
      //指定版本號
      args1_1.version = JNI_VERSION_1_1;
      //通過虛擬機返回一個指定的參數值,該方法時從libjvm.so裏面導出的函數指針
      ifn->GetDefaultJavaVMInitArgs(&args1_1);  
      if (args1_1.javaStackSize > 0) {
         //如果查詢到有效值,則會修改全局定義的棧大小
         threadStackSize = args1_1.javaStackSize;
      }
    }
    {
      /* 創建一個新線程去創建jvm,然後調用main方法*/
      JavaMainArgs args;
      int rslt;

      args.argc = argc;
      args.argv = argv;
      args.mode = mode;
      args.what = what;
      args.ifn = *ifn;

      /**/
      rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
      /* If the caller has deemed there is an error we
       * simply return that, otherwise we return the value of
       * the callee
       */
      return (ret != 0) ? ret : rslt;
    }
}

如果沒設置threadStackSize,從GetDefaultJavaVMInitArgs獲取並設置,
規整參數調用ContinueInNewThread0

ContinueInNewThread0

作用:嘗試創建新線程執行代碼邏輯,創建新線程失敗則在當前線程執行代碼邏輯
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c

/*
 * 暫停當前線程,然後繼續執行一個新的線程
 */
int
ContinueInNewThread0(
                     int (JNICALL *continuation)(void *),/線程入口函數,在這裏具體執行的時JavaMain方法
					 jlong stack_size, //線程棧大小,會使用該值創建線程
					 void * args//參數
) {
    int rslt;
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);//初始化線程
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);//可join線程

    if (stack_size > 0) {// //如果給定的線程棧大小值有效,則設置創建線程的棧大小,否則使用默認值
      pthread_attr_setstacksize(&attr, stack_size);
    }

     /* 調用線程庫創建線程,並設定入口函數爲JavaMain */
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      pthread_join(tid, &tmp);////創建成功後,並等待線程的結束,主線程掛起
      rslt = (int)tmp;
    } else {
     /*
      * 如果因爲某些條件導致創建線程失敗,則在當前線程執行JavaMain方法
      */
      rslt = continuation(args);
    }

    pthread_attr_destroy(&attr);//線程回收
    return rslt;
}

創建一線程執行JavaMain函數

JavaMain

作用:虛擬機的入口函數
位置:jdk8u-dev/jdk/src/share/bin/java.c

int JNICALL
JavaMain(void * _args)
{
    JavaMainArgs *args = (JavaMainArgs *)_args;         //獲取參數
    int argc = args->argc;
    char **argv = args->argv;
    int mode = args->mode;
    char *what = args->what;
    InvocationFunctions ifn = args->ifn;                //當前虛擬機導致的函數指針
                                                        //該機制可以保證同一環境配置多個jdk版本

    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;                            //main函數class
    jclass appClass = NULL;                             // 正在啓動的實際應用程序類
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start, end;

    RegisterThread();                                   //window/類unix爲空實現,macos特別處理

    /* 初始化虛擬機 */
    start = CounterGet();
    //通過CreateJavaVM導出jvm到&vm, &env
    //&vm主要提供虛擬整體的操作
    //&env主要提供虛擬啓動的環境的操作
    if (!InitializeJVM(&vm, &env, &ifn)) {                                                                      
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }

    if (showSettings != NULL) {
        ShowSettings(env, showSettings);
        CHECK_EXCEPTION_LEAVE(1);
    }

    if (printVersion || showVersion) {
        PrintJavaVersion(env, showVersion);
        CHECK_EXCEPTION_LEAVE(0);
        if (printVersion) {
            LEAVE();
        }
    }

    /* 如果沒有指定class或者jar,則直接退出 */
    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    FreeKnownVMs();  /* after last possible PrintUsage() */

    /* 記錄初始化jvm時間 */
    if (JLI_IsTraceLauncher()) {
        end = CounterGet();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
               (long)(jint)Counter2Micros(end-start));
    }

    /* 接下來,從argv獲取應用的參數,打印參數 */
    if (JLI_IsTraceLauncher()){
        int i;
        printf("%s is '%s'\n", launchModeNames[mode], what);
        printf("App's argc is %d\n", argc);
        for (i=0; i < argc; i++) {
            printf("    argv[%2d] = '%s'\n", i, argv[i]);
        }
    }

    ret = 1;

    /*
     * Get the application's main class.
     *
     * See bugid 5030265.  The Main-Class name has already been parsed
     * from the manifest, but not parsed properly for UTF-8 support.
     * Hence the code here ignores the value previously extracted and
     * uses the pre-existing code to reextract the value.  This is
     * possibly an end of release cycle expedient.  However, it has
     * also been discovered that passing some character sets through
     * the environment has "strange" behavior on some variants of
     * Windows.  Hence, maybe the manifest parsing code local to the
     * launcher should never be enhanced.
     *
     * Hence, future work should either:
     *     1)   Correct the local parsing code and verify that the
     *          Main-Class attribute gets properly passed through
     *          all environments,
     *     2)   Remove the vestages of maintaining main_class through
     *          the environment (and remove these comments).
     *
     * This method also correctly handles launching existing JavaFX
     * applications that may or may not have a Main-Class manifest entry.
     */

    /* 加載Main class */
    //從環境中取出java主類
    mainClass = LoadMainClass(env, mode, what);
    /* 檢查是否指定main class, 不存在則退出虛擬機*/
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
   
    /* 獲取應用的class文件 */
	//JavaFX gui應用相關可忽略
    appClass = GetApplicationClass(env);
    /* 檢查是否指定app class, 不存在返回-1 */
    NULL_CHECK_RETURN_VALUE(appClass, -1);
    
    /*window和類unix爲空實現,在macos下設定gui程序的程序名稱*/
	//JavaFX gui應用相關可忽略
    PostJVMInit(env, appClass, vm);
    
    /*從mainclass中加載靜態main方法*/
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);

    /* Build platform specific argument array */
    //組裝main函數參數
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

    /* Invoke main method. */
    /* 調用main方法,並把參數傳遞過去 */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * The launcher's exit code (in the absence of calls to
     * System.exit) will be non-zero if main threw an exception.
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    //main方法執行完畢,JVM退出,包含兩步:
    //(*vm)->DetachCurrentThread,讓當前Main線程同啓動線程斷聯
    //創建一個新的名爲DestroyJavaVM的線程,讓該線程等待所有的非後臺進程退出,並在最後執行(*vm)->DestroyJavaVM方法。
    LEAVE();
}
  1. InitializeJVM
    初始化JVM,通過調用JNI_CreateJavaVM給JavaVM和JNIEnv對象正確賦值
  2. LoadMainClass
    獲取應用程序的MainClass,即包含java程序啓動入口main方法的類,
  3. GetApplicationClass
    JavaFX沒有MainClass而是通過ApplicationClass啓動的,這裏獲取ApplicationClass
  4. PostJVMInit 將ApplicationClass作爲應用名傳給JavaFX本身,比如作爲主菜單
  5. (*env)->GetStaticMethodID
    獲取main方法的方法ID
  6. CreateApplicationArgs
    解析main方法的參數
  7. (*env)->CallStaticVoidMethod 執行main方法
  8. LEAVE
    main方法執行完畢,JVM退出

JNI_CreateJavaVM

作用:創建java虛擬機
位置:jdk8u-dev/hotspot/src/share/vm/prims/jni.cpp

/* 導出JNI_CreateJavaVM函數 */
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
#ifndef USDT2
  HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
#else /* USDT2 */
  HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
                                 (void **) vm, penv, args);
#endif /* USDT2 */

  jint result = JNI_ERR;
  DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);

  // We're about to use Atomic::xchg for synchronization.  Some Zero
  // platforms use the GCC builtin __sync_lock_test_and_set for this,
  // but __sync_lock_test_and_set is not guaranteed to do what we want
  // on all architectures.  So we check it works before relying on it.
#if defined(ZERO) && defined(ASSERT)
  {
    jint a = 0xcafebabe;
    jint b = Atomic::xchg(0xdeadbeef, &a);
    void *c = &a;
    void *d = Atomic::xchg_ptr(&b, &c);
    assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
    assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
  }
#endif // ZERO && ASSERT

  // At the moment it's only possible to have one Java VM,
  // since some of the runtime state is in global variables.

  // We cannot use our mutex locks here, since they only work on
  // Threads. We do an atomic compare and exchange to ensure only
  // one thread can call this method at a time

  // We use Atomic::xchg rather than Atomic::add/dec since on some platforms
  // the add/dec implementations are dependent on whether we are running
  // on a multiprocessor, and at this stage of initialization the os::is_MP
  // function used to determine this will always return false. Atomic::xchg
  // does not have this problem.
  if (Atomic::xchg(1, &vm_created) == 1) {
    return JNI_EEXIST;   // already created, or create attempt in progress
  }
  if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
    return JNI_ERR;  // someone tried and failed and retry not allowed.
  }

  assert(vm_created == 1, "vm_created is true during the creation");

  /**
  * 初始化期間的某些錯誤是可恢復的,並且不會阻止稍後再次調用此方法(可能使用不同的參數)。 
  * 但是,在初始化期間的某個時刻如果發生錯誤,我們不能再次調用此函數(否則它將崩潰)。 
  * 在這些情況下,ccan_try_again標誌設置爲false,它將 safe_to_recreate_vm 原子地設置爲1,
  * 這樣任何對JNI_CreateJavaVM的新調用都將立即使用上述邏輯失敗。
  */
  bool can_try_again = true;

  /* 創建java虛擬機 */
  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  if (result == JNI_OK) {
    JavaThread *thread = JavaThread::current();
    /* thread is thread_in_vm here */
    /* 獲取vm對象,從全局的main_vm賦值 */
    *vm = (JavaVM *)(&main_vm);
    /* 獲取線程jni環境指針 */
    *(JNIEnv**) penv = thread->jni_environment();

    // Tracks the time application was running before GC
    // 記錄程序啓動時間,跟從應用程序在gc前的運行時間
    RuntimeService::record_application_start();

    
    // 通知JVMTI應用啓動
    if (JvmtiExport::should_post_thread_life()) {
       JvmtiExport::post_thread_start(thread);
    }

    EventThreadStart event;
    if (event.should_commit()) {
      event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
      event.commit();
    }

#ifndef PRODUCT
  #ifndef TARGET_OS_FAMILY_windows
    #define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
  #endif

     //根據配置加載類路徑中所有的類
    if (CompileTheWorld) ClassLoader::compile_the_world();
    if (ReplayCompiles) ciReplay::replay(thread);

    // win* 系統添加異常處理的wrapper
    CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
    CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif

     //設置當前線程的線程狀態
    ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
  } else {
    /* 創建失敗,如果允許重試,則修改safe_to_recreate_vm標誌 */
    if (can_try_again) {
      // reset safe_to_recreate_vm to 1 so that retrial would be possible
      safe_to_recreate_vm = 1;
    }

    // 創建失敗,需要重新設置vm_create值
    *vm = 0;
    *(JNIEnv**)penv = 0;
    // reset vm_created last to avoid race condition. Use OrderAccess to
    // control both compiler and architectural-based reordering.
    OrderAccess::release_store(&vm_created, 0);
  }
  return result;
}

調用Threads::create_vm創建虛擬機,並設置vm,env返回

Threads::create_vm

作用:hotspot創建java虛擬機函數
位置:jdk8u-dev/hotspot/src/share/vm/runtime/thread.cpp

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
  //調用JDK_Version_init();
  //加載libjava.so、libverify.so庫,通過調用庫中導出的JDK_GetVersionInfo0查詢當前虛擬機版本
  extern void JDK_Version_init();

  // 檢查當前版本是否在支持範圍內,主要不支持1.1版本 高於1.1都返回true
  if (!is_supported_jni_version(args->version)) return JNI_EVERSION;

  //初始化系統輸出流模塊
  ostream_init();

  // 處理java啓動屬性.
  Arguments::process_sun_java_launcher_properties(args);

  // Initialize the os module before using TLS
  // 初始化系統環境,例如:獲取當前的進程pid、獲取系統時鐘、設置內存頁大小
  // 獲取cpu數、獲取物理內存大小
  os::init();

  // Initialize system properties.
  // 設置系統屬性, key-value
  // 設置了java虛擬機信息、清空由os::init_system_properties_values()方法設置的值
  Arguments::init_system_properties();

  // So that JDK version can be used as a discrimintor when parsing arguments
  // 獲取jdk版本號,作爲接下來參數解析的依據
  JDK_Version_init();

  // Update/Initialize System properties after JDK version number is known
  // 設定jdk版本後再去更新系統版本、供應商屬性值,此處主要設定3個參數
  // jdk1.7以後的版本廠商修改爲oracle,之前爲sun
  // 1、java.vm.specification.vendor     可選值爲:Oracle Corporation / Sun Microsystems Inc.
  // 2、java.vm.specification.version    可選值爲:1.0  /  1.7  1.8  1.9 etc
  // 3、java.vm.vendor                   可選值爲: Oracle Corporation / Sun Microsystems Inc.
  Arguments::init_version_specific_system_properties();

  // Parse arguments
  // 解析參數,生成java虛擬機運行期間的一些參數指標
  // -XX:Flags=   -XX:+PrintVMOptions  -XX:-PrintVMOptions    -XX:+IgnoreUnrecognizedVMOptions
  // -XX:-IgnoreUnrecognizedVMOptions  -XX:+PrintFlagsInitial -XX:NativeMemoryTracking
  jint parse_result = Arguments::parse(args);
  if (parse_result != JNI_OK) return parse_result;

  //主要完成大頁支持以及linux上的coredump_filter配置
  os::init_before_ergo();

  //調整運行環境得參數及一些指標
  //1、設置參數及指標  如果是server模式並且沒有指定gc策略,則默認使用UsePaallelGC
  // 64位下啓用普通對象壓縮
  //2、設置是否使用共享存儲空間(開啓對象壓縮、指針壓縮後關閉)
  //3、檢查gc一致性
  //4、依據物理內存設置對內存大小
  //5、設置各種gc的參數標誌:parallel、cms、g1、panew
  //6、初始化jvm的平衡因子參數
  //7、設定字節碼重寫標誌
  //8、如果指定-XX:+AggressiveOpts參數,則設定加快編譯,依賴於EliminateAutoBox及DoEscapeAnalysis標誌位及C2下
  //9、根據是否指定gama lanucher及是否是調試狀態設定暫停狀態位
  jint ergo_result = Arguments::apply_ergo();
  if (ergo_result != JNI_OK) return ergo_result;

  //暫停
  if (PauseAtStartup) {
    os::pause();
  }

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__begin);
#else /* USDT2 */
  HOTSPOT_VM_INIT_BEGIN();
#endif /* USDT2 */

  // Record VM creation timing statistics
  //記錄jvm啓動開始時間
  TraceVmCreationTime create_vm_timer;
  create_vm_timer.start();

  // Timing (must come after argument parsing)
  TraceTime timer("Create VM", TraceStartupTime);

  // Initialize the os module after parsing the args
  //解析參數後,初始化系統模塊
  //1、如果使用linux下的posix線程庫cpu時鐘的話,使用pthread_getcpuclockid方法獲取jvm線程
  //   的cpu時鐘id,然後基於這個線程的時鐘進行時間統計計數
  //2、分配一個linux內存單頁並標記位爲可循環讀的安全入口點
  //3、初始化暫停/恢復運行的支持,主要通過註冊信號處理程序支持該功能
  //4、註冊信號處理程序
  //5、安裝信號處理程序
  //6、計算線程棧大小
  //7、設置glibc、linux線程版本信息
  //8、設置linux系統進程文件描述符值
  //9、創建用於線程創建的線程鎖
  //11、初始化線程的優先級
  jint os_init_2_result = os::init_2();
  if (os_init_2_result != JNI_OK) return os_init_2_result;

  //調整參數,主要針對numa架構下ParallelGC、OarallelOldGc調整堆內存參數
  jint adjust_after_os_result = Arguments::adjust_after_os();
  if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;

  // intialize TLS
  // 初始化線程本地存儲,通過ptread_create_key創建一個線程特有的key_t,
  // 後面通過pthread_setspecific\pthread_getspecific設置、獲取線程私有數據
  ThreadLocalStorage::init();

  // Bootstrap native memory tracking, so it can start recording memory
  // activities before worker thread is started. This is the first phase
  // of bootstrapping, VM is currently running in single-thread mode.
  // 啓動本地內存跟蹤,因此它可以在工作線程啓動之前開始記錄內存活動。 
  // 這是引導的第一階段,JVM當前以單線程模式運行。
  MemTracker::bootstrap_single_thread();

  // Initialize output stream logging
  // 初始化jvm的日誌輸出流,如果指定-Xloggc:logfilepath則根據參數指定生成輸出流到文件
  ostream_init_log();

  // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
  // Must be before create_vm_init_agents()
  // 如果指定了-Xrun參數,如:hprof性能分析、jdwp遠程調試
  if (Arguments::init_libraries_at_startup()) {
    convert_vm_init_libraries_to_agents();
  }

  // Launch -agentlib/-agentpath and converted -Xrun agents
  if (Arguments::init_agents_at_startup()) {
    create_vm_init_agents();
  }

  // Initialize Threads state
  _thread_list = NULL;
  _number_of_threads = 0;
  _number_of_non_daemon_threads = 0;

  // Initialize global data structures and create system classes in heap
  // 1、初始化全局結構體
  // 2、在內存中創建jvm體系的class文件
  // 3、初始化事件記錄日誌對象
  // 4、初始化全局資源鎖mutex
  // 5、內存池初始化large_pool、medium_pool、small_poll、tiny_pool
  // 6、啓用該perfdata功能。此選項默認啓用以允許JVM監視和性能測試,如果指定 -XX:+UsePerfData
  vm_init_globals();

  // Attach the main thread to this os thread
  // 創建新的主java線程, 設置主線程狀態爲運行在jvm裏面
  JavaThread* main_thread = new JavaThread();
  main_thread->set_thread_state(_thread_in_vm);
  // must do this before set_active_handles and initialize_thread_local_storage
  // Note: on solaris initialize_thread_local_storage() will (indirectly)
  // change the stack size recorded here to one based on the java thread
  // stacksize. This adjusted size is what is used to figure the placement
  // of the guard pages.
  // 記錄棧基址、棧大小值
  main_thread->record_stack_base_and_size();
  // 初始化主線程的本地存儲
  main_thread->initialize_thread_local_storage();

  // 綁定jniHandleBlock指針, 處理jni邏輯句柄
  main_thread->set_active_handles(JNIHandleBlock::allocate_block());

  // 設定main_thread爲主線程
  if (!main_thread->set_as_starting_thread()) {
    vm_shutdown_during_initialization(
      "Failed necessary internal allocation. Out of swap space");
    delete main_thread;
    *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
    return JNI_ENOMEM;
  }

  // Enable guard page *after* os::create_main_thread(), otherwise it would
  // crash Linux VM, see notes in os_linux.cpp.
  main_thread->create_stack_guard_pages();

  // Initialize Java-Level synchronization subsystem
  ObjectMonitor::Initialize() ;

  // Second phase of bootstrapping, VM is about entering multi-thread mode
  MemTracker::bootstrap_multi_thread();

  // Initialize global modules
  // 初始化全局模塊
  // 
  jint status = init_globals();
  if (status != JNI_OK) {
    delete main_thread;
    *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
    return status;
  }

  // Should be done after the heap is fully created
  main_thread->cache_global_variables();

  HandleMark hm;

  { MutexLocker mu(Threads_lock);
    Threads::add(main_thread);
  }

  // Any JVMTI raw monitors entered in onload will transition into
  // real raw monitor. VM is setup enough here for raw monitor enter.
  JvmtiExport::transition_pending_onload_raw_monitors();

  // Fully start NMT
  MemTracker::start();

  // Create the VMThread
  { TraceTime timer("Start VMThread", TraceStartupTime);
    VMThread::create();
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread))
      vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }

  assert (Universe::is_fully_initialized(), "not initialized");
  if (VerifyDuringStartup) {
    // Make sure we're starting with a clean slate.
    VM_Verify verify_op;
    VMThread::execute(&verify_op);
  }

  EXCEPTION_MARK;

  // At this point, the Universe is initialized, but we have not executed
  // any byte code.  Now is a good time (the only time) to dump out the
  // internal state of the JVM for sharing.
  if (DumpSharedSpaces) {
    MetaspaceShared::preload_and_dump(CHECK_0);
    ShouldNotReachHere();
  }

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_start_phase();

  // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
  JvmtiExport::post_vm_start();

  {
    TraceTime timer("Initialize java.lang classes", TraceStartupTime);

    if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
      create_vm_init_libraries();
    }

    initialize_class(vmSymbols::java_lang_String(), CHECK_0);

    // Initialize java_lang.System (needed before creating the thread)
    initialize_class(vmSymbols::java_lang_System(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
    Handle thread_group = create_initial_thread_group(CHECK_0);
    Universe::set_main_thread_group(thread_group());
    //裝載Thread class對象
    initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
    //創建thread實例
    oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
    main_thread->set_threadObj(thread_object);
    // Set thread status to running since main thread has
    // been started and running.
    java_lang_Thread::set_thread_status(thread_object,
                                        java_lang_Thread::RUNNABLE);

    // The VM creates & returns objects of this class. Make sure it's initialized.
    initialize_class(vmSymbols::java_lang_Class(), CHECK_0);

    // The VM preresolves methods to these classes. Make sure that they get initialized
    initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ref_Finalizer(),  CHECK_0);
    call_initializeSystemClass(CHECK_0);

    // get the Java runtime name after java.lang.System is initialized
    JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
    JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));

    // an instance of OutOfMemory exception has been allocated earlier
    initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
    initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
    initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
  }

  // See        : bugid 4211085.
  // Background : the static initializer of java.lang.Compiler tries to read
  //              property"java.compiler" and read & write property "java.vm.info".
  //              When a security manager is installed through the command line
  //              option "-Djava.security.manager", the above properties are not
  //              readable and the static initializer for java.lang.Compiler fails
  //              resulting in a NoClassDefFoundError.  This can happen in any
  //              user code which calls methods in java.lang.Compiler.
  // Hack :       the hack is to pre-load and initialize this class, so that only
  //              system domains are on the stack when the properties are read.
  //              Currently even the AWT code has calls to methods in java.lang.Compiler.
  //              On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
  // Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
  //              read and write"java.vm.info" in the default policy file. See bugid 4211383
  //              Once that is done, we should remove this hack.
  initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);

  // More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
  // the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
  // compiler does not get loaded through java.lang.Compiler).  "java -version" with the
  // hotspot vm says "nojit" all the time which is confusing.  So, we reset it here.
  // This should also be taken out as soon as 4211383 gets fixed.
  reset_vm_info_property(CHECK_0);

  quicken_jni_functions();

  // Must be run after init_ft which initializes ft_enabled
  if (TRACE_INITIALIZE() != JNI_OK) {
    vm_exit_during_initialization("Failed to initialize tracing backend");
  }

  // Set flag that basic initialization has completed. Used by exceptions and various
  // debug stuff, that does not work until all basic classes have been initialized.
  set_init_completed();

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__end);
#else /* USDT2 */
  HOTSPOT_VM_INIT_END();
#endif /* USDT2 */

  // record VM initialization completion time
#if INCLUDE_MANAGEMENT
  Management::record_vm_init_completed();
#endif // INCLUDE_MANAGEMENT

  // Compute system loader. Note that this has to occur after set_init_completed, since
  // valid exceptions may be thrown in the process.
  // Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
  // set_init_completed has just been called, causing exceptions not to be shortcut
  // anymore. We call vm_exit_during_initialization directly instead.
  SystemDictionary::compute_java_system_loader(THREAD);
  if (HAS_PENDING_EXCEPTION) {
    vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
  }

#if INCLUDE_ALL_GCS
  // Support for ConcurrentMarkSweep. This should be cleaned up
  // and better encapsulated. The ugly nested if test would go away
  // once things are properly refactored. XXX YSR
  if (UseConcMarkSweepGC || UseG1GC) {
    if (UseConcMarkSweepGC) {
      ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
    } else {
      ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
    }
    if (HAS_PENDING_EXCEPTION) {
      vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
    }
  }
#endif // INCLUDE_ALL_GCS

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_live_phase();

  // Signal Dispatcher needs to be started before VMInit event is posted
  os::signal_init();

  // Start Attach Listener if +StartAttachListener or it can't be started lazily
  if (!DisableAttachMechanism) {
    AttachListener::vm_start();
    if (StartAttachListener || AttachListener::init_at_startup()) {
      AttachListener::init();
    }
  }

  // Launch -Xrun agents
  // Must be done in the JVMTI live phase so that for backward compatibility the JDWP
  // back-end can launch with -Xdebug -Xrunjdwp.
  if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
    create_vm_init_libraries();
  }

  // Notify JVMTI agents that VM initialization is complete - nop if no agents.
  JvmtiExport::post_vm_initialized();

  if (TRACE_START() != JNI_OK) {
    vm_exit_during_initialization("Failed to start tracing backend.");
  }

  if (CleanChunkPoolAsync) {
    Chunk::start_chunk_pool_cleaner_task();
  }

  // initialize compiler(s)
#if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
  CompileBroker::compilation_init();
#endif

  if (EnableInvokeDynamic) {
    // Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
    // It is done after compilers are initialized, because otherwise compilations of
    // signature polymorphic MH intrinsics can be missed
    // (see SystemDictionary::find_method_handle_intrinsic).
    initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
  }

#if INCLUDE_MANAGEMENT
  Management::initialize(THREAD);
#endif // INCLUDE_MANAGEMENT

  if (HAS_PENDING_EXCEPTION) {
    // management agent fails to start possibly due to
    // configuration problem and is responsible for printing
    // stack trace if appropriate. Simply exit VM.
    vm_exit(1);
  }

  if (Arguments::has_profile())       FlatProfiler::engage(main_thread, true);
  if (MemProfiling)                   MemProfiler::engage();
  StatSampler::engage();
  if (CheckJNICalls)                  JniPeriodicChecker::engage();

  BiasedLocking::init();

  if (JDK_Version::current().post_vm_init_hook_enabled()) {
    call_postVMInitHook(THREAD);
    // The Java side of PostVMInitHook.run must deal with all
    // exceptions and provide means of diagnosis.
    if (HAS_PENDING_EXCEPTION) {
      CLEAR_PENDING_EXCEPTION;
    }
  }

  {
      MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
      // Make sure the watcher thread can be started by WatcherThread::start()
      // or by dynamic enrollment.
      WatcherThread::make_startable();
      // Start up the WatcherThread if there are any periodic tasks
      // NOTE:  All PeriodicTasks should be registered by now. If they
      //   aren't, late joiners might appear to start slowly (we might
      //   take a while to process their first tick).
      if (PeriodicTask::num_tasks() > 0) {
          WatcherThread::start();
      }
  }

  // Give os specific code one last chance to start
  os::init_3();

  create_vm_timer.end();
#ifdef ASSERT
  _vm_complete = true;
#endif
  return JNI_OK;
}

此方法真正創建的jvm 其中JNIEnv對象的初始化在 JavaThread* main_thread = new
JavaThread()時, JavaThread()這裏調用JavaThread(bool is_attaching_via_jni =
false)的構造方法,
然後執行initialize()-》set_jni_functions(jni_functions());完成初始化,
jni_functions()方法返回thread.cpp中的一個全局變量

主要參考

《hotspot實戰》
OpenJdk1.8筆記–java啓動流程

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