深入Java虚拟机笔记(五):剖析HotSpot的Launcher

 2019-12-22 11:00  阅读(774)
文章分类:JVM

介绍

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

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

HotSpot VM源码目录结构

├─agent                            Serviceability Agent的实现 

    ├─make                             用来build出HotSpot的各种配置文件 

    ├─src                              HotSpot VM的源代码 

    │  ├─cpu                            CPU相关代码 

    │  ├─os                             操作系相关代码 

    │  ├─os_cpu                         操作系统+CPU的组合相关的代码 

    │  └─share                          平台无关的共通代码 

    │      ├─tools                        工具 

    │      │  ├─hsdis                      反汇编插件 

    │      │  ├─IdealGraphVisualizer       将server编译器的中间代码可视化的工具 

    │      │  ├─launcher                   启动程序“java” 

    │      │  ├─LogCompilation             将-XX:+LogCompilation输出的日志(hotspot.log)整理成更容易阅读的格式的工具 

    │      │  └─ProjectCreator             生成Visual Studio的project文件的工具 

    │      └─vm                           HotSpot VM的核心代码 

    │          ├─adlc                       平台描述文件(上面的cpu或os_cpu里的*.ad文件)的编译器 

    │          ├─asm                        汇编器接口 

    │          ├─c1                         client编译器 

    │          ├─ci                         动态编译器的公共服务/接口 

    │          ├─classfile                  类文件的处理(包括类加载和系统符号表等) 

    │          ├─code                       动态生成的代码的管理 

    │          ├─compiler                   编译器接口 

    │          ├─gc_implementation          GC的实现 

    │          │  ├─concurrentMarkSweep      Concurrent Mark Sweep GC的实现 

    │          │  ├─g1                       Garbage-First GC的实现(不使用老的分代式GC框架) 

    │          │  ├─parallelScavenge         ParallelScavenge GC的实现(server VM默认,不使用老的分代式GC框架) 

    │          │  ├─parNew                   ParNew GC的实现 

    │          │  └─shared                   GC的共通实现 

    │          ├─gc_interface               GC的接口 

    │          ├─interpreter                解释器,包括“模板解释器”(官方版在用)和“C++解释器”(官方版不在用) 

    │          ├─libadt                     一些抽象数据结构 

    │          ├─memory                     内存管理相关(老的分代式GC框架也在这里) 

    │          ├─oops                       HotSpot VM的对象系统的实现 

    │          ├─opto                       server编译器 

    │          ├─prims                      HotSpot VM的对外接口,包括部分标准库的native部分和JVMTI实现 

    │          ├─runtime                    运行时支持库(包括线程管理、编译器调度、锁、反射等) 

    │          ├─services                   主要是用来支持JMX之类的管理功能的接口 

    │          ├─shark                      基于LLVM的JIT编译器(官方版里没有使用) 

    │          └─utilities                  一些基本的工具类 

    └─test                             单元测试

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

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

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

Launcher的执行过程

2019120001466\_1.png

1、启动函数main()

(1)Launcher启动后,对与运行环境有关的局部变量进行初始化。该局部变量在后面创建运行环境需要用到,在调用JavaMain()函数也需要传递过去

char *jarfile = 0;
        char *classname = 0;
        char *s = 0;
        char *main_class = NULL;
        int ret;
        InvocationFunctions ifn;
        jlong start, end;
        char jrepath[MAXPATHLEN], jvmpath[MAXPATHLEN];
        char ** original_argv = argv;

(2)创建运行环境

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

        printf("Using java runtime at: %s\n", jrepath);

(3)在函数程序末尾,创建一个新的线程去执行JVM的初始化和正式调用Java程序main()方法

struct JavaMainArgs args;

          args.argc = argc;
          args.argv = argv;
          args.jarfile = jarfile;
          args.classname = classname;
          args.ifn = ifn;

          return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args);

2、在主线程中执行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()函数

//LoadClass()用于获取Java程序的启动类
     mainClass = LoadClass(env, classname);
     //GetStaticMethodID()用于获取Java程序的启动方法
     mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                           "([Ljava/lang/String;)V");

接下来,调用CallStaticVoidMethod()执行Java程序的Main()方法

(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

当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);

附上相关源码

Main()函数全部源码

int main(int argc, char ** argv)
    {
        char *jarfile = 0;
        char *classname = 0;
        char *s = 0;
        char *main_class = NULL;
        int ret;
        InvocationFunctions ifn;
        jlong start, end;
        char jrepath[MAXPATHLEN], jvmpath[MAXPATHLEN];
        char ** original_argv = argv;

        if (getenv("_JAVA_LAUNCHER_DEBUG") != 0) {
            _launcher_debug = JNI_TRUE;
            printf("----_JAVA_LAUNCHER_DEBUG----\n");
        }

    #ifndef GAMMA
        /*
         * Make sure the specified version of the JRE is running.
         *
         * There are three things to note about the SelectVersion() routine:
         *  1) If the version running isn't correct, this routine doesn't
         *     return (either the correct version has been exec'd or an error
         *     was issued).
         *  2) Argc and Argv in this scope are *not* altered by this routine.
         *     It is the responsibility of subsequent code to ignore the
         *     arguments handled by this routine.
         *  3) As a side-effect, the variable "main_class" is guaranteed to
         *     be set (if it should ever be set).  This isn't exactly the
         *     poster child for structured programming, but it is a small
         *     price to pay for not processing a jar file operand twice.
         *     (Note: This side effect has been disabled.  See comment on
         *     bugid 5030265 below.)
         */
        SelectVersion(argc, argv, &main_class);
    #endif /* ifndef GAMMA */

        /* copy original argv */
        {
          int i;
          original_argv = (char**)JLI_MemAlloc(sizeof(char*)*(argc+1));
          for(i = 0; i < argc+1; i++)
            original_argv[i] = argv[i];
        }

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

        printf("Using java runtime at: %s\n", jrepath);

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

        if (_launcher_debug)
          start = CounterGet();
        if (!LoadJavaVM(jvmpath, &ifn)) {
          exit(6);
        }
        if (_launcher_debug) {
          end   = CounterGet();
          printf("%ld micro seconds to LoadJavaVM\n",
                 (long)(jint)Counter2Micros(end-start));
        }

    #ifdef JAVA_ARGS /* javac, jar and friends. */
        progname = "java";
    #else /* java, oldjava, javaw and friends */
    #ifdef PROGNAME
        progname = PROGNAME;
    #else
        progname = *argv;
        if ((s = strrchr(progname, FILE_SEPARATOR)) != 0) {
            progname = s + 1;
        }
    #endif /* PROGNAME */
    #endif /* JAVA_ARGS */
        ++argv;
        --argc;

    #ifdef JAVA_ARGS
        /* Preprocess wrapper arguments */
        TranslateApplicationArgs(&argc, &argv);
        if (!AddApplicationOptions()) {
            exit(1);
        }
    #endif

        /* Set default CLASSPATH */
        if ((s = getenv("CLASSPATH")) == 0) {
            s = ".";
        }
    #ifndef JAVA_ARGS
        SetClassPath(s);
    #endif

        /*
         *  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.
         */
        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 */
          struct JavaMainArgs args;

          args.argc = argc;
          args.argv = argv;
          args.jarfile = jarfile;
          args.classname = classname;
          args.ifn = ifn;
     return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args);
        }
    }

JavaMain()函数全部源码

JavaMain(void * _args)
    {
        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;

        /*
         * Error message to print or display; by default the message will
         * only be displayed in a window.
         */
        char * message = "Fatal exception occurred. Program will exit.";
        jboolean messageDest = JNI_FALSE;

        /* Initialize the virtual machine */

        if (_launcher_debug)
            start = CounterGet();
        if (!InitializeJVM(&vm, &env, &ifn)) {
            ReportErrorMessage("Could not create the Java virtual machine.",
                               JNI_TRUE);
            exit(1);
        }

        if (printVersion || showVersion) {
            PrintJavaVersion(env);
            if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } if (printVersion) { ret = 0; message = NULL; goto leave; } if (showVersion) { fprintf(stderr, "\n"); } } /* If the user specified neither a class name nor a JAR file */ if (jarfile == 0 && classname == 0) { PrintUsage(); message = NULL; goto leave; } #ifndef GAMMA FreeKnownVMs(); /* after last possible PrintUsage() */ #endif if (_launcher_debug) { end = CounterGet(); printf("%ld micro seconds to InitializeJVM\n", (long)(jint)Counter2Micros(end-start)); } /* At this stage, argc/argv have the applications' arguments */ if (_launcher_debug) { int i = 0; printf("Main-Class is '%s'\n", classname ? classname : ""); printf("Apps' argc is %d\n", argc); for (; 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). */ if (jarfile != 0) { mainClassName = GetMainClassName(env, jarfile); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } if (mainClassName == NULL) { const char * format = "Failed to load Main-Class manifest " "attribute from\n%s"; message = (char*)JLI_MemAlloc((strlen(format) + strlen(jarfile)) * sizeof(char)); sprintf(message, format, jarfile); messageDest = JNI_TRUE; goto leave; } classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0);
            if (classname == NULL) {
                ReportExceptionDescription(env);
                goto leave;
            }
            mainClass = LoadClass(env, classname);
            if(mainClass == NULL) { /* exception occured */
                const char * format = "Could not find the main class: %s. Program will exit.";
                ReportExceptionDescription(env);
                message = (char *)JLI_MemAlloc((strlen(format) +
                                                strlen(classname)) * sizeof(char) );
                messageDest = JNI_TRUE;
                sprintf(message, format, classname);
                goto leave;
            }
            (*env)->ReleaseStringUTFChars(env, mainClassName, classname);
        } else {
          mainClassName = NewPlatformString(env, classname);
          if (mainClassName == NULL) {
            const char * format = "Failed to load Main Class: %s";
            message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) *
                                       sizeof(char) );
            sprintf(message, format, classname);
            messageDest = JNI_TRUE;
            goto leave;
          }
          classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0);
          if (classname == NULL) {
            ReportExceptionDescription(env);
            goto leave;
          }
          mainClass = LoadClass(env, classname);
          if(mainClass == NULL) { /* exception occured */
            const char * format = "Could not find the main class: %s. Program will exit.";
            ReportExceptionDescription(env);
            message = (char *)JLI_MemAlloc((strlen(format) +
                                            strlen(classname)) * sizeof(char) );
            messageDest = JNI_TRUE;
            sprintf(message, format, classname);
            goto leave;
          }
          (*env)->ReleaseStringUTFChars(env, mainClassName, classname);
        }

        /* Get the application's main method */
        mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                           "([Ljava/lang/String;)V");
        if (mainID == NULL) {
            if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); } else { message = "No main method found in specified class."; messageDest = JNI_TRUE; } goto leave; } { /* Make sure the main method is public */ jint mods; jmethodID mid; jobject obj = (*env)->ToReflectedMethod(env, mainClass,
                                                    mainID, JNI_TRUE);

            if( obj == NULL) { /* exception occurred */
                ReportExceptionDescription(env);
                goto leave;
            }

            mid = (*env)->GetMethodID(env,
                                  (*env)->GetObjectClass(env, obj),
                                  "getModifiers", "()I");
            if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } mods = (*env)->CallIntMethod(env, obj, mid);
            if ((mods & 1) == 0) { /* if (!Modifier.isPublic(mods)) ... */
                message = "Main method not public.";
                messageDest = JNI_TRUE;
                goto leave;
            }
        }

        /* Build argument array */
        mainArgs = NewPlatformStringArray(env, argv, argc);
        if (mainArgs == NULL) {
            ReportExceptionDescription(env);
            goto leave;
        }

        /* 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; /* * Detach the main thread so that it appears to have ended when * the application's main method exits.  This will invoke the
         * uncaught exception handler machinery if main threw an
         * exception.  An uncaught exception handler cannot change the
         * launcher's return code except by calling System.exit. */ if ((*vm)->DetachCurrentThread(vm) != 0) { message = "Could not detach main thread."; messageDest = JNI_TRUE; ret = 1; goto leave; }int message = NULL; leave: /* * Wait for all non-daemon threads to end, then destroy the VM. * This will actually create a trivial new Java waiter thread * named "DestroyJavaVM", but this will be seen as a different * thread from the one that executed main, even though they are * the same C thread. This allows mainThread.join() and * mainThread.isAlive() to work as expected. */ (*vm)->DestroyJavaVM(vm); if(message != NULL && !noExitErrorMessage) ReportErrorMessage(message, messageDest); return ret; }

参考:《Java虚拟机精讲》

点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 深入Java虚拟机笔记(五):剖析HotSpot的Launcher

相关推荐