当前位置 博文首页 > 等你归去来:JVM系列(三):JVM创建过程解析

    等你归去来:JVM系列(三):JVM创建过程解析

    作者:等你归去来 时间:2021-02-16 16:33

      上两篇中梳理了整个java启动过程中,jvm大致是如何运行的。即厘清了我们认为的jvm的启动过程。但那里面仅为一些大致的东西,比如参数解析,验证,dll加载等等。把最核心的loadJavaVM()交给了一个dll或者so库。也就是真正的jvm我们并没有接触到,我们仅看了一个包装者或者是上层应用的实现。即我们仅是在jdk的角度看了下虚拟机,这需要更深入一点。

     

    1. 回顾jvm加载框架

      虽然jvm的加载核心并不在jdk中,但它确实没有自己的简易入口。也就是说jvm想要启动,还得依靠jdk. 所以,让我们回顾下jdk是如何带动jvm的?

    1.1. java启动框架

      自然是在 JLI_Launch 的入口查看了。

    // share/bin/java.c
    /*
     * Entry point.
     */
    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);
        }
        /*
         * 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.)
         */
        // 解析命令行参数,选择一jre版本
        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();
        }
        // 加载VM, 重中之重
        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();
        // 进行jvm初始化操作,一般是新开一个线程,然后调用 JavaMain() 实现java代码的权力交接
        return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
    }

      以上就是java启动jvm的核心框架。和真正的jvm相关的两个:1. SelectVersion() 会查找系统中存在的jvm即jre版本,是否可以被当前使用,以及main_class的验证;2. 在初始化时会调用jvm的 CreateJavaVM()方法,进行jvm真正的创建交接,这是通过函数指针实现的;

      具体两个相关操作需要分解下,因为这些过程还是略微复杂的。

     

    1.2. jre的查找定位与验证

      要运行jvm,首先就是要确定系统中是否安装了相应的jre环境,并确定版本是否正确。

    // java.c
    /*
     * The SelectVersion() routine ensures that an appropriate version of
     * the JRE is running.  The specification for the appropriate version
     * is obtained from either the manifest of a jar file (preferred) or
     * from command line options.
     * The routine also parses splash screen command line options and
     * passes on their values in private environment variables.
     */
    static void
    SelectVersion(int argc, char **argv, char **main_class)
    {
        char    *arg;
        char    **new_argv;
        char    **new_argp;
        char    *operand;
        char    *version = NULL;
        char    *jre = NULL;
        int     jarflag = 0;
        int     headlessflag = 0;
        int     restrict_search = -1;               /* -1 implies not known */
        manifest_info info;
        char    env_entry[MAXNAMELEN + 24] = ENV_ENTRY "=";
        char    *splash_file_name = NULL;
        char    *splash_jar_name = NULL;
        char    *env_in;
        int     res;
        /*
         * If the version has already been selected, set *main_class
         * with the value passed through the environment (if any) and
         * simply return.
         */
        // _JAVA_VERSION_SET=
        if ((env_in = getenv(ENV_ENTRY)) != NULL) {
            if (*env_in != '\0')
                *main_class = JLI_StringDup(env_in);
            return;
        }
        /*
         * Scan through the arguments for options relevant to multiple JRE
         * support.  For reference, the command line syntax is defined as:
         *
         * SYNOPSIS
         *      java [options] class [argument...]
         *
         *      java [options] -jar file.jar [argument...]
         *
         * As the scan is performed, make a copy of the argument list with
         * the version specification options (new to 1.5) removed, so that
         * a version less than 1.5 can be exec'd.
         *
         * Note that due to the syntax of the native Windows interface
         * CreateProcess(), processing similar to the following exists in
         * the Windows platform specific routine ExecJRE (in java_md.c).
         * Changes here should be reproduced there.
         */
        new_argv = JLI_MemAlloc((argc + 1) * sizeof(char*));
        new_argv[0] = argv[0];
        new_argp = &new_argv[1];
        argc--;
        argv++;
        while ((arg = *argv) != 0 && *arg == '-') {
            if (JLI_StrCCmp(arg, "-version:") == 0) {
                version = arg + 9;
            } else if (JLI_StrCmp(arg, "-jre-restrict-search") == 0) {
                restrict_search = 1;
            } else if (JLI_StrCmp(arg, "-no-jre-restrict-search") == 0) {
                restrict_search = 0;
            } else {
                if (JLI_StrCmp(arg, "-jar") == 0)
                    jarflag = 1;
                /* deal with "unfortunate" classpath syntax */
                if ((JLI_StrCmp(arg, "-classpath") == 0 || JLI_StrCmp(arg, "-cp") == 0) &&
                  (argc >= 2)) {
                    *new_argp++ = arg;
                    argc--;
                    argv++;
                    arg = *argv;
                }
                /*
                 * Checking for headless toolkit option in the some way as AWT does:
                 * "true" means true and any other value means false
                 */
                if (JLI_StrCmp(arg, "-Djava.awt.headless=true") == 0) {
                    headlessflag = 1;
                } else if (JLI_StrCCmp(arg, "-Djava.awt.headless=") == 0) {
                    headlessflag = 0;
                } else if (JLI_StrCCmp(arg, "-splash:") == 0) {
                    splash_file_name = arg+8;
                }
                *new_argp++ = arg;
            }
            argc--;
            argv++;
        }
        if (argc <= 0) {    /* No operand? Possibly legit with -[full]version */
            operand = NULL;
        } else {
            argc--;
            *new_argp++ = operand = *argv++;
        }
        while (argc-- > 0)  /* Copy over [argument...] */
            *new_argp++ = *argv++;
        *new_argp = NULL;
        /*
         * If there is a jar file, read the manifest. If the jarfile can't be
         * read, the manifest can't be read from the jar file, or the manifest
         * is corrupt, issue the appropriate error messages and exit.
         *
         * Even if there isn't a jar file, construct a manifest_info structure
         * containing the command line information.  It's a convenient way to carry
         * this data around.
         */
        if (jarflag && operand) {
            if ((res = JLI_ParseManifest(operand, &info)) != 0) {
                if (res == -1)
                    JLI_ReportErrorMessage(JAR_ERROR2, operand);
                else
                    JLI_ReportErrorMessage(JAR_ERROR3, operand);
                exit(1);
            }
            /*
             * Command line splash screen option should have precedence
             * over the manifest, so the manifest data is used only if
             * splash_file_name has not been initialized above during command
             * line parsing
             */
            if (!headlessflag && !splash_file_name && info.splashscreen_image_file_name) {
                splash_file_name = info.splashscreen_image_file_name;
                splash_jar_name 
    
    下一篇:没有了