美文网首页
Java Hotspot虚拟机的启动过程(三)

Java Hotspot虚拟机的启动过程(三)

作者: buzzerrookie | 来源:发表于2019-03-03 10:23 被阅读0次

    上一篇文章提到新线程继续运行JavaMain函数的代码,本文简要分析该函数。

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

    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请求了。

    point of no return和V1决断速度很像,:)

    LoadMainClass函数

    TODO

    GetApplicationClass函数

    TODO

    PostJVMInit函数

    TODO

    其他工作

    TODO

    相关文章

      网友评论

          本文标题:Java Hotspot虚拟机的启动过程(三)

          本文链接:https://www.haomeiwen.com/subject/ytclyqtx.html