Skip to content

Latest commit

 

History

History
868 lines (712 loc) · 31.2 KB

3. HotSpot的启动.md

File metadata and controls

868 lines (712 loc) · 31.2 KB

##剖析HotSpot的启动

参考链接:

###介绍

HotSpot属于OpenJDK项目的一个功能子集,HotSpot目录下四大子目录:

  • agent:包含Serviceability Agent的客户端的实现
  • make:用于build出HotSpot的各种配置文件
  • src:包括HotSpot的所有源码
  • test:单元测试

###Launcher启动器

Launcher是用于启动JVM进程的启动器,有两种,

  • 一种windows平台下运行时会保留在控制台
  • 一种用于执行Java的GUI程序,不会显示任何程序的输出信息

Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和windows平台下的jvm.dll动态链接库。

###Launcher的执行过程

Launcher是一种用于启动JVM进程的启动器,是一个封装了虚拟机的执行外壳,他负责装载JRE环境和Windows平台下的jvm.dll动态链接库(linux平台下则是libjvm.so,并不是虚拟机的实现)。 在一个JVM的进程内部,只能执行一个指定的Java程序,也就是当执行 多个java程序时,就启动了多个JVM进程。Launcher是JVM的启动器 ,那么必然会由他负责调用HotSpot的核心代码对JVM执行初始化,以及由他负责维护JVM的整个生命周期。具体流程如下所示:

  1. 进入Launcher的启动函数main()。(main函数主要负责创建运行环境,以及启动一个全新的线程去执行JVM的初始化和调用java程序的main方法)
  2. 启动一个全新的线程调用JavaMain()函数(负责调用InitializeJVM()函数,但InitializeJVM()函数本身并不具备初始化JVM的能力,而是由他调用本地函数JNI_InitializeJVM()函数去完成真正意义上的JVM初始化)。
  3. JVM初始化完成后,Launcher调用LoadClass()函数和GetStaticMethodId()函数,分别获取Java程序的启动类和启动方法。
  4. Launcher会调用本地函数jni_CallStaticVoidMethod()执行java程序的main()方法。
  5. 最后Launcher调用本地函数jni_DetachCurrentThread()断开与主线程的连接。(断开与主线程的连接之后,Launcher会一直等待程序中所有的非守护线程(non_daemonthread)全部执行结束)
  6. 调用本地函数jni_DestoryJavaVM()对JVM执行销毁。

启动入口在/home/openjdk/openjdk-jdk8u-master/jdk/src/share/bin/main.c:97,查看代码可知其核心启动方法是JLI_Launch

#include "defines.h"

int main(int argc, char **argv)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_FALSE;
    margc = argc;
    margv = argv;
    return JLI_Launch(margc, margv,
                   sizeof(const_jargs) / sizeof(char *), const_jargs,
                   sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
                   FULL_VERSION,
                   DOT_VERSION,
                   (const_progname != NULL) ? const_progname : *margv,
                   (const_launcher != NULL) ? const_launcher : *margv,
                   (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
                   const_cpwildcard, const_javaw, const_ergo_class);
}

JLI_Launch函数的代码如下所示:

/*
 * Shared source for 'java' command line tool.
 *
 * If JAVA_ARGS is defined, then acts as a launcher for applications. For
 * instance, the JDK command line tools such as javac and javadoc (see
 * makefiles for more details) are built with this program.  Any arguments
 * prefixed with '-J' will be passed directly to the 'java' command.
 */
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;
    char *what = NULL;
    char *cpath = 0;
    char *main_class = NULL;
    int ret;
    InvocationFunctions ifn;
    jlong start, end;
    char jvmpath[MAXPATHLEN];
    char jrepath[MAXPATHLEN];
    char jvmcfg[MAXPATHLEN];

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

    InitLauncher(javaw);
    DumpState();
    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);
    }

    SelectVersion(argc, argv, &main_class);

    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));

    if (!IsJavaArgs()) {
        SetJvmEnvironment(argc,argv);
    }

    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJavaVMInitArgs = 0;

    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }

    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }

    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
    }

    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
             (long)(jint)Counter2Micros(end-start));

    ++argv;
    --argc;

    if (IsJavaArgs()) {
        /* Preprocess wrapper arguments */
        TranslateApplicationArgs(jargc, jargv, &argc, &argv);
        if (!AddApplicationOptions(appclassc, appclassv)) {
            return(1);
        }
    } else {
        /* Set default CLASSPATH */
        cpath = getenv("CLASSPATH");
        if (cpath == NULL) {
            cpath = ".";
        }
        SetClassPath(cpath);
    }

    /* Parse command line options; if the return value of
     * ParseArguments is false, the program should exit.
     */
    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
    {
        return(ret);
    }

    /* Override class path if -jar flag was specified */
    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();

    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

简言之,JLI_Launch函数主要做了以下事情:

  • 将参数保存到全局变量,如版本号、文件名、是否定义了java参数(即JAVA_ARGS宏是否定义)等;
  • InitLauncher函数设置调试开关,如果环境变量_JAVA_LAUNCHER_DEBUG有定义则开启Launcher的调试模式,后续调用JLI_IsTraceLauncher函数会返回真(即值1);
  • SelectVersion函数解析-version:release、-jre-restrict-search、-no-jre-restrict-search和-splash:imgname 选项,确保运行适当版本的JRE(注意不要将JRE的版本与JVM的数据模型搞混)。需要的JRE版本既可以从命令行选项-version:release指定,也可以在jar包的META-INF/MANIFEST.MF文件中用JRE-Version键指定;
  • CreateExecutionEnvironment函数为后续创建虚拟机选择了数据模型,去除-d32、-J-d32、-d64和-J-d64选项;
  • LoadJavaVM函数从JVM动态库获取函数指针;
  • 解析其他命令行选项;
  • 设置额外的虚拟机启动选项;
  • 初始化JVM。

其主要流程如下:

  • SelectVersion,从jar包中manifest文件或者命令行读取用户使用的JDK版本,判断当前版本是否合适

  • CreateExecutionEnvironment,设置执行环境参数

  • LoadJavaVM,加载libjvm动态链接库,从中获取JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs三个函数的实现,其中JNI_CreateJavaVM是JVM初始化的核心入口,具体实现在hotspot目录中

jdk/src/java.base/unix/native/libjli/java_md_solinux.c
加载libjvm.so,绑定InvocationFunctions

jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){ //
    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");
   return JNI_TRUE;
}
  • ParseArguments 解析命令行参数,如-version,-help等参数在该方法中解析的

  • SetJavaCommandLineProp 解析形如-Dsun.java.command=的命令行参数

  • SetJavaLauncherPlatformProps 解析形如-Dsun.java.launcher.*的命令行参数

/*
     *  Parse command line options; if the return value of
     *  ParseArguments is false, the program should exit.
     */
    if (!ParseArguments(&argc, &argv, &jarfile, &classname, &ret, jvmpath)) {
      exit(ret);
    }

    /* Override class path if -jar flag was specified */
    if (jarfile != 0) {
        SetClassPath(jarfile);
    }

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

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

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

#ifndef GAMMA
    /* Show the splash screen if needed */
    ShowSplashScreen();
#endif

    /*
     * Done with all command line processing and potential re-execs so
     * clean up the environment.
     */
    (void)UnsetEnv(ENV_ENTRY);
#ifndef GAMMA
    (void)UnsetEnv(SPLASH_FILE_ENV_ENTRY);
    (void)UnsetEnv(SPLASH_JAR_ENV_ENTRY);

    JLI_MemFree(splash_jar_entry);
    JLI_MemFree(splash_file_entry);
#endif

    /*
     * If user doesn't specify stack size, check if VM has a preference.
     * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
     * return its default stack size through the init args structure.
     */

命令行参数被解析后,虚拟机启动选项已经构造完毕,JLI_Launch函数最后调用JVMInit函数在新的线程中初始化虚拟机。

  • JVMInit,通过JVMInit->ContinueInNewThread->ContinueInNewThread0->pthread_create创建了一个新的线程,执行JavaMain函数,主线程pthread_join该线程,在JavaMain函数中完成虚拟机的初始化和启动。

JVMInit函数定义在文件jdk/src/solaris/bin/java_md_solinux.c中,相关代码如下:

int JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
        int argc, char **argv,
        int mode, char *what, int ret)
{
    ShowSplashScreen();
    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}

ifn结构体保存了先前用LoadJavaVM函数在JVM动态库中查找到的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs函数指针。

int ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
                    int argc, char **argv,
                    int mode, char *what, int ret)
{
    /*
     * If user doesn't specify stack size, check if VM has a preference.
     * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
     * return its default stack size through the init args structure.
     */
    if (threadStackSize == 0) {
      struct JDK1_1InitArgs args1_1;
      memset((void*)&args1_1, 0, sizeof(args1_1));
      args1_1.version = JNI_VERSION_1_1;
      ifn->GetDefaultJavaVMInitArgs(&args1_1);  /* ignore return value */
      if (args1_1.javaStackSize > 0) {
         threadStackSize = args1_1.javaStackSize;
      }
    }

    { /* Create a new thread to create JVM and invoke main method */
      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;
    }
}

ContinueInNewThread函数的参数分别是:

  • ifn保存了JVM动态库的函数指针;
  • argc和argv分别是传递给第一个操作数的参数个数和参数列表;
  • mode是启动模式,从类启动还是从jar启动;
  • what是第一个操作数。
int ContinueInNewThread0(int (JNICALL *continuation)(void *), 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);

    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size);
    }

    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      pthread_join(tid, &tmp);
      rslt = (int)tmp;
    } else {
     /*
      * Continue execution in current thread if for some reason (e.g. out of
      * memory/LWP)  a new thread can't be created. This will likely fail
      * later in continuation as JNI_CreateJavaVM needs to create quite a
      * few new threads, anyway, just give it a try..
      */
      rslt = continuation(args);
    }

    pthread_attr_destroy(&attr);
    return rslt;
}

在ContinueInNewThread0中,父线程首先利用pthread库函数创建新线程执行JavaMain函数的代码,然后调用pthread_join函数将自己阻塞。

####在主线程中执行JavaMain()函数

当main()函数完成运行环境的创建后,接下来就任务交给在主线程中执行JavaMain()函数,JavaMain()函数会从Main()函数中传递过来的一些变量参数,然后初始化几个比较重要的局部变量

//从Main()函数中传递过来的一些变量参数
struct JavaMainArgs *args = (struct JavaMainArgs *)_args;
    int argc = args->argc;
    char **argv = args->argv;
    char *jarfile = args->jarfile;
    char *classname = args->classname;
    InvocationFunctions ifn = args->ifn;
//初始化几个比较重要的局部变量
    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jstring mainClassName;
    jclass mainClass;
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start, end;

结构体JavaMainArgs如下

struct JavaMainArgs {
  int     argc;
  char ** argv;
  char *  jarfile;
  char *  classname;
  InvocationFunctions ifn;
};

结构体JavaMainArgs内部的 InvocationFunctions类型中包含的函数指针与对应的目标函数,如下:

ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;

函数指针所指向的目标函数主要用于完成断开主线程和执行JVM销毁等功能,如下:

(*vm)->DetachCurrentThread(vm)
(*vm)->DestroyJavaVM(vm)

当函数指针成功指向目标函数和JVM初始化后,Launcher会执行LoadClass()函数和GetStaticMethodID()函数

执行JavaMain函数

int JNICALL JavaMain(void * _args) {
    JavaMainArgs *args = (JavaMainArgs *)_args;
    ......
    InvocationFunctions ifn = args->ifn;
    //初始化虚拟机
    start = CounterGet();
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    ......
    //加载Hello类
   mainClass = LoadMainClass(env, mode, what); 
   appClass = GetApplicationClass(env);
   //获取Hello类的main方法
   mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
  //调用main()方法
  (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
  
  //结束
  LEAVE();
}

接下来,调用CallStaticVoidMethod()执行Java程序的Main()方法,当Java程序执行完后,断开与主线程的连接

  if ((*vm)->DetachCurrentThread(vm) != 0) {
        message = "Could not detach main thread.";
        messageDest = JNI_TRUE;
        ret = 1;
        goto leave;
    }int

当断开连接后,Launcher等待所有非守护线程全部执行完成,最后销毁JVM

(*vm)->DestroyJavaVM(vm);

####JavaMain函数源码解析

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;

    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;
    jclass appClass = NULL; // actual application class being launched
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start, end;

    RegisterThread();

    /* Initialize the virtual machine */
    start = CounterGet();
    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();
        }
    }

    /* If the user specified neither a class name nor a JAR file */
    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

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

    if (JLI_IsTraceLauncher()) {
        end = CounterGet();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
               (long)(jint)Counter2Micros(end-start));
    }

    /* At this stage, argc/argv have the application's arguments */
    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.
     */
    mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    /*
     * In some cases when launching an application that needs a helper, e.g., a
     * JavaFX application with no main method, the mainClass will not be the
     * applications own main class but rather a helper class. To keep things
     * consistent in the UI we need to track and report the application main class.
     */
    appClass = GetApplicationClass(env);
    NULL_CHECK_RETURN_VALUE(appClass, -1);
    /*
     * PostJVMInit uses the class name as the application name for GUI purposes,
     * for example, on OSX this sets the application name in the menu bar for
     * both SWT and JavaFX. So we'll pass the actual application class here
     * instead of mainClass as that may be a launcher or helper class instead
     * of the application class.
     */
    PostJVMInit(env, appClass, vm);
    CHECK_EXCEPTION_LEAVE(1);
    /*
     * 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.
     */
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);

    /* Build platform specific argument array */
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

    /* Invoke main method. */
    (*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;
    LEAVE();
}

ifn结构体保存了先前用LoadJavaVM函数在JVM动态库中查找到的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs函数指针。

####InitializeJVM函数

InitializeJVM函数进一步初始化JVM,其代码如下:

/*
 * 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;

    if (JLI_IsTraceLauncher()) {
        int i = 0;
        printf("JavaVM args:\n    ");
        printf("version 0x%08lx, ", (long)args.version);
        printf("ignoreUnrecognized is %s, ",
               args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
        printf("nOptions is %ld\n", (long)args.nOptions);
        for (i = 0; i < numOptions; i++)
            printf("    option[%2d] = '%s'\n",
                   i, args.options[i].optionString);
    }

    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    JLI_MemFree(options);
    return r == JNI_OK;
}

args结构体表示JVM启动选项,全局变量options指向先前TranslateApplicationArgs函数和ParseArguments函数添加或解析的JVM启动选项,另一个全局变量numOptions则保存了选项个数;ifn结构体的CreateJavaVM函数指针即指向JVM动态库中的JNI_CreateJavaVM函数。

####JNI_CreateJavaVM函数

JNI_CreateJavaVM函数定义在文件hotspot/src/share/vm/prims/jni.cpp中,预处理后的代码如下所示:

JNIIMPORT jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
  jint result = (-1);
  if (Atomic::xchg(1, &vm_created) == 1) {
    return (-5);
  }
  if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
    return (-1);
  }
  bool can_try_again = true;

  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  if (result == 0) {
    JavaThread *thread = JavaThread::current();

    *vm = (JavaVM *)(&main_vm);
    *(JNIEnv**)penv = thread->jni_environment();

    RuntimeService::record_application_start();

    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();
    }

    if (CompileTheWorld) ClassLoader::compile_the_world();
    if (ReplayCompiles) ciReplay::replay(thread);

    test_error_handler();
    execute_internal_vm_tests();
    ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
  } else {
    if (can_try_again) {
      safe_to_recreate_vm = 1;
    }
    *vm = 0;
    *(JNIEnv**)penv = 0;
    OrderAccess::release_store(&vm_created, 0);
  }
  return result;
}


jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
  .....
  //TLS初始化
  ThreadLocalStorage::init();
  // 输入输出流初始化
  ostream_init();
  //os模块初始化
  os::init();
  os::init_before_ergo();
  //VM初始化开始
  HOTSPOT_VM_INIT_BEGIN();
  //初始化全局数据结构并在堆中创建系统类
  vm_init_globals();
  //Attach线程
  JavaThread* main_thread = new JavaThread();
  main_thread->set_thread_state(_thread_in_vm);
  main_thread->initialize_thread_current();
  // 线程栈基和栈大小
  main_thread->record_stack_base_and_size();
  main_thread->set_active_handles(JNIHandleBlock::allocate_block());
  // 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();
  //初始化Java级同步子系统
  ObjectMonitor::Initialize();
  // 初始化全局模块
  jint status = init_globals();
  
  // Should be done after the heap is fully created
  main_thread->cache_global_variables();
  //初始化java/lang下的类
  initialize_java_lang_classes(main_thread, CHECK_JNI_ERR);
  //标记初始化完成
  set_init_completed();
  //元空间初始化
  Metaspace::post_initialize();
  HOTSPOT_VM_INIT_END();

  // 预先初始化一些JSR292核心类,String,System,Class类
  initialize_jsr292_core_classes(CHECK_JNI_ERR);

  // 这将初始化模块系统。只有java.base类可以是加载到阶段2完成
  call_initPhase2(CHECK_JNI_ERR);

  // 即使还没有JVMTI环境,也要始终调用,因为环境可能会延迟连接,而且JVMTI必须跟踪VM执行的各个阶段
  JvmtiExport::enter_start_phase();

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

  //最终系统初始化,包括安全管理器和系统类加载器
  call_initPhase3(CHECK_JNI_ERR);

  // 缓存系统类加载器
  SystemDictionary::compute_java_system_loader(CHECK_(JNI_ERR));

  //即使还没有JVMTI环境,也要始终调用,因为环境可能会延迟连接,而且JVMTI必须跟踪VM执行的各个阶段
  JvmtiExport::enter_live_phase();

  //通知初始化完成
  JvmtiExport::post_vm_initialized();
  ......
  
#ifdef ASSERT
  _vm_complete = true;
#endif
  return JNI_OK; //JVM初始化完成

}

hotspot/src/share/vm/runtime/init.cpp
初始化全局数据结构并在堆中创建系统类

void vm_init_globals() {
  check_ThreadShadow();
  basic_types_init(); //heapOopSize与压缩指针
  eventlog_init(); //一些事件
  mutex_init();// 全局监视器和互斥体
  chunkpool_init();//四个ChunkPool
  perfMemory_init();//监控内存初始化
  SuspendibleThreadSet_init();
}

hotspot/src/share/vm/runtime/init.cpp

初始化全局模块
jint init_globals() {
  HandleMark hm;
  management_init(); //监控服务,线程服务,运行时服务,类加载服务初始化
  bytecodes_init(); // 字节码解释器初始化
  classLoader_init1(); //类加载器初始化第一阶段:zip,jimage入口,设置启动类路径
  compilationPolicy_init();//编译策略,根据编译等级确定从c1,c2编译器
  codeCache_init();//代码缓存初始化
  VM_Version_init();
  os_init_globals();
  stubRoutines_init1();//例程初始化第一阶段,例程:代码调用点
  //堆空间,元空间,AOTLoader,SymbolTable,StringTable,G1收集器初始化
  jint status = universe_init(); 
  if (status != JNI_OK)
    return status;

  interpreter_init();  // 模板解释器初始化
  invocationCounter_init();  //热点统计初始化
  marksweep_init();//标记清除GC初始化
  accessFlags_init();
  templateTable_init();//模板表初始化
  InterfaceSupport_init();
  SharedRuntime::generate_stubs();//生成部分例程入口
  universe2_init();  // vmSymbols,系统字典,预加载类,构建基本数据对象模型
  referenceProcessor_init();//对象引用策略
  jni_handles_init();//JNI引用句柄
#if INCLUDE_VM_STRUCTS
  vmStructs_init(); //一些相关数据结构
#endif // INCLUDE_VM_STRUCTS

  vtableStubs_init();//虚表例程初始化
  InlineCacheBuffer_init();//内联缓冲初始化
  compilerOracle_init();//Oracle相关的初始化
  dependencyContext_init();//监控统计相关

  if (!compileBroker_init()) {
    return JNI_EINVAL;
  }
  VMRegImpl::set_regName(); //虚拟机寄存器初始化

  if (!universe_post_init()) {//初始化必要的类,注册堆空间到内存服务
    return JNI_ERR;
  }
  javaClasses_init();   //java基础类相关计算
  stubRoutines_init2(); // 例程初始化第二阶段,模板解释器继承体系初始化
  MethodHandles::generate_adapters();//方法与代码缓存点适配

  return JNI_OK;
}

HotSpot的初始化过程中集中体现在init_globals()中各模块的初始化,从最基础的监控,服务,类加载器初始化为后续各阶段的初始化做准备。后续包括解释器,类加载器,堆空间,编译器,收集器等各模块逐一初始化为HotSpot的运行做好准备。

OpenJDK总结了JNI_CreateJavaVM函数的工作:

  1. 保证同一时间只有一个线程调用此方法,并且同一进程中只能创建一个虚拟机实例。请注意一旦到达了不可返回点(point of no return),虚拟机就不能被再次创建,这是因为虚拟机创建的静态数据结构不能被重新初始化;
  2. 检查JNI版本是否被支持,为GC日志初始化输出流,初始化OS模块如随机数生成器、高分辨率时间、内存页大小和保护页(guard page)等;
  3. 解析并保存传入的参数和属性供后续使用,初始化标准的Java系统属性;
  4. OS模块被进一步初始化,根据解析的参数和属性初始化同步机制、栈、内存和safepoint page。此时其他库如libzip、libhpi、libjava和libthread被加载,初始化并设置信号处理函数和线程库;
  5. 初始化输出流,初始化和启动需要的Agent库如hprof、jdi等。
  6. 初始化线程状态和线程局部存储(TLS);
  7. 初始化全局数据,如事件日志、OS同步原语等;
  8. 此时开始创建线程,Java里的main线程被创建并被附加(attach)到当前OS线程,然而这个线程还没有被加入线程链表。初始化并启用Java层的同步机制;
  9. 初始化其他全局模块如BootClassLoader、CodeCache、Interpreter、Compiler、JNI、SystemDictionary和Universe,此时到达了不可返回点,不可以在相同进程的地址空间创建另一个虚拟机了;
  10. 将main线程加入线程链表。执行虚拟机关键功能的VMThread被创建;
  11. 初始化并加载Java类,如java.lang.String、java.lang.System、java.lang.Thread、java.lang.ThreadGroup、java.lang.reflect.Method、java.lang.ref.Finalizer、java.lang.Class和System。此时虚拟机已被初始化并可运行,但还不是完全可用;
  12. 启动信号处理线程,编译器被初始化,启动CompileBroker线程。其他辅助线程如StatSampler和WatcherThread也被启动,此时虚拟机已经完全可用,JNIEnv接口指针被填充并返回给调用者,虚拟机已经准备好服务新的JNI请求了。

至此HotSpot虚拟机初始化完毕,将返回java.c的JavaMain函数继续往下加载Hello主类并找到Hello类的静态main方法并执行,执行完毕后LEAVE(),退出。

LoadMainClass函数

TODO

GetApplicationClass函数

TODO

PostJVMInit函数

TODO

其他工作

TODO