zoukankan      html  css  js  c++  java
  • JVM系列(三):JVM创建过程解析

      上两篇中梳理了整个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:
    ");
            for (i = 0; i < argc ; i++) {
                printf("argv[%d] = %s
    ", 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
    ",
                 (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 != '')
                *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 = operand;
            }
        } else {
            info.manifest_version = NULL;
            info.main_class = NULL;
            info.jre_version = NULL;
            info.jre_restrict_search = 0;
        }
        /*
         * Passing on splash screen info in environment variables
         */
        if (splash_file_name && !headlessflag) {
            char* splash_file_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_FILE_ENV_ENTRY "=")+JLI_StrLen(splash_file_name)+1);
            JLI_StrCpy(splash_file_entry, SPLASH_FILE_ENV_ENTRY "=");
            JLI_StrCat(splash_file_entry, splash_file_name);
            putenv(splash_file_entry);
        }
        if (splash_jar_name && !headlessflag) {
            char* splash_jar_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_JAR_ENV_ENTRY "=")+JLI_StrLen(splash_jar_name)+1);
            JLI_StrCpy(splash_jar_entry, SPLASH_JAR_ENV_ENTRY "=");
            JLI_StrCat(splash_jar_entry, splash_jar_name);
            putenv(splash_jar_entry);
        }
        /*
         * The JRE-Version and JRE-Restrict-Search values (if any) from the
         * manifest are overwritten by any specified on the command line.
         */
        if (version != NULL)
            info.jre_version = version;
        if (restrict_search != -1)
            info.jre_restrict_search = restrict_search;
        /*
         * "Valid" returns (other than unrecoverable errors) follow.  Set
         * main_class as a side-effect of this routine.
         */
        if (info.main_class != NULL)
            *main_class = JLI_StringDup(info.main_class);
        /*
         * If no version selection information is found either on the command
         * line or in the manifest, simply return.
         */
        if (info.jre_version == NULL) {
            JLI_FreeManifest();
            JLI_MemFree(new_argv);
            return;
        }
        /*
         * Check for correct syntax of the version specification (JSR 56).
         */
        if (!JLI_ValidVersionString(info.jre_version)) {
            JLI_ReportErrorMessage(SPC_ERROR1, info.jre_version);
            exit(1);
        }
        /*
         * Find the appropriate JVM on the system. Just to be as forgiving as
         * possible, if the standard algorithms don't locate an appropriate
         * jre, check to see if the one running will satisfy the requirements.
         * This can happen on systems which haven't been set-up for multiple
         * JRE support.
         */
        jre = LocateJRE(&info);
        JLI_TraceLauncher("JRE-Version = %s, JRE-Restrict-Search = %s Selected = %s
    ",
            (info.jre_version?info.jre_version:"null"),
            (info.jre_restrict_search?"true":"false"), (jre?jre:"null"));
        if (jre == NULL) {
            if (JLI_AcceptableRelease(GetFullVersion(), info.jre_version)) {
                JLI_FreeManifest();
                JLI_MemFree(new_argv);
                return;
            } else {
                JLI_ReportErrorMessage(CFG_ERROR4, info.jre_version);
                exit(1);
            }
        }
        /*
         * If I'm not the chosen one, exec the chosen one.  Returning from
         * ExecJRE indicates that I am indeed the chosen one.
         *
         * The private environment variable _JAVA_VERSION_SET is used to
         * prevent the chosen one from re-reading the manifest file and
         * using the values found within to override the (potential) command
         * line flags stripped from argv (because the target may not
         * understand them).  Passing the MainClass value is an optimization
         * to avoid locating, expanding and parsing the manifest extra
         * times.
         */
        if (info.main_class != NULL) {
            if (JLI_StrLen(info.main_class) <= MAXNAMELEN) {
                (void)JLI_StrCat(env_entry, info.main_class);
            } else {
                JLI_ReportErrorMessage(CLS_ERROR5, MAXNAMELEN);
                exit(1);
            }
        }
        (void)putenv(env_entry);
        ExecJRE(jre, new_argv);
        JLI_FreeManifest();
        JLI_MemFree(new_argv);
        return;
    }
    
    // jre的定位过程
    // solaris/bin/java_md_common.c
    /*
     *      This is the global entry point. It examines the host for the optimal
     *      JRE to be used by scanning a set of directories.  The set of directories
     *      is platform dependent and can be overridden by the environment
     *      variable JAVA_VERSION_PATH.
     *
     *      This routine itself simply determines the set of appropriate
     *      directories before passing control onto ProcessDir().
     */
    char*
    LocateJRE(manifest_info* info)
    {
        char        *path;
        char        *home;
        char        *target = NULL;
        char        *dp;
        char        *cp;
    
        /*
         * Start by getting JAVA_VERSION_PATH
         */
        if (info->jre_restrict_search) {
            path = JLI_StringDup(system_dir);
        } else if ((path = getenv("JAVA_VERSION_PATH")) != NULL) {
            path = JLI_StringDup(path);
        } else {
            if ((home = getenv("HOME")) != NULL) {
                path = (char *)JLI_MemAlloc(JLI_StrLen(home) + 
                            JLI_StrLen(system_dir) + JLI_StrLen(user_dir) + 2);
                sprintf(path, "%s%s:%s", home, user_dir, system_dir);
            } else {
                path = JLI_StringDup(system_dir);
            }
        }
    
        /*
         * Step through each directory on the path. Terminate the scan with
         * the first directory with an acceptable JRE.
         */
        cp = dp = path;
        while (dp != NULL) {
            cp = JLI_StrChr(dp, (int)':');
            if (cp != NULL)
                *cp = '';
            if ((target = ProcessDir(info, dp)) != NULL)
                break;
            dp = cp;
            if (dp != NULL)
                dp++;
        }
        JLI_MemFree(path);
        return (target);
    }
    
    // 尝试执行jre, 以验证其是否有效
    // solaris/bin/java_md_common.c
    /*
     * Given a path to a jre to execute, this routine checks if this process
     * is indeed that jre.  If not, it exec's that jre.
     *
     * We want to actually check the paths rather than just the version string
     * built into the executable, so that given version specification (and
     * JAVA_VERSION_PATH) will yield the exact same Java environment, regardless
     * of the version of the arbitrary launcher we start with.
     */
    void
    ExecJRE(char *jre, char **argv)
    {
        char    wanted[PATH_MAX];
        const char* progname = GetProgramName();
        const char* execname = NULL;
    
        /*
         * Resolve the real path to the directory containing the selected JRE.
         */
        if (realpath(jre, wanted) == NULL) {
            JLI_ReportErrorMessage(JRE_ERROR9, jre);
            exit(1);
        }
    
        /*
         * Resolve the real path to the currently running launcher.
         */
        SetExecname(argv);
        execname = GetExecName();
        if (execname == NULL) {
            JLI_ReportErrorMessage(JRE_ERROR10);
            exit(1);
        }
    
        /*
         * If the path to the selected JRE directory is a match to the initial
         * portion of the path to the currently executing JRE, we have a winner!
         * If so, just return.
         */
        if (JLI_StrNCmp(wanted, execname, JLI_StrLen(wanted)) == 0)
            return;                 /* I am the droid you were looking for */
    
    
        /*
         * This should never happen (because of the selection code in SelectJRE),
         * but check for "impossibly" long path names just because buffer overruns
         * can be so deadly.
         */
        if (JLI_StrLen(wanted) + JLI_StrLen(progname) + 6 > PATH_MAX) {
            JLI_ReportErrorMessage(JRE_ERROR11);
            exit(1);
        }
    
        /*
         * Construct the path and exec it.
         */
        (void)JLI_StrCat(JLI_StrCat(wanted, "/bin/"), progname);
        argv[0] = JLI_StringDup(progname);
        if (JLI_IsTraceLauncher()) {
            int i;
            printf("ReExec Command: %s (%s)
    ", wanted, argv[0]);
            printf("ReExec Args:");
            for (i = 1; argv[i] != NULL; i++)
                printf(" %s", argv[i]);
            printf("
    ");
        }
        JLI_TraceLauncher("TRACER_MARKER:About to EXEC
    ");
        (void)fflush(stdout);
        (void)fflush(stderr);
        execv(wanted, argv);
        JLI_ReportErrorMessageSys(JRE_ERROR12, wanted);
        exit(1);
    }

      接下来有个环境准备的过程,有点复杂。主要就是根据不同的平台要求,设置一些环境变量,想看更多的同学自行展开。

    void
    CreateExecutionEnvironment(int *pargc, char ***pargv,
                               char jrepath[], jint so_jrepath,
                               char jvmpath[], jint so_jvmpath,
                               char jvmcfg[],  jint so_jvmcfg) {
      /*
       * First, determine if we are running the desired data model.  If we
       * are running the desired data model, all the error messages
       * associated with calling GetJREPath, ReadKnownVMs, etc. should be
       * output.  However, if we are not running the desired data model,
       * some of the errors should be suppressed since it is more
       * informative to issue an error message based on whether or not the
       * os/processor combination has dual mode capabilities.
       */
        jboolean jvmpathExists;
    
        /* Compute/set the name of the executable */
        SetExecname(*pargv);
    
        /* Check data model flags, and exec process, if needed */
        {
          char *arch        = (char *)GetArch(); /* like sparc or sparcv9 */
          char * jvmtype    = NULL;
          int  argc         = *pargc;
          char **argv       = *pargv;
          int running       = CURRENT_DATA_MODEL;
    
          int wanted        = running;      /* What data mode is being
                                               asked for? Current model is
                                               fine unless another model
                                               is asked for */
    #ifdef SETENV_REQUIRED
          jboolean mustsetenv = JNI_FALSE;
          char *runpath     = NULL; /* existing effective LD_LIBRARY_PATH setting */
          char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */
          char* newpath     = NULL; /* path on new LD_LIBRARY_PATH */
          char* lastslash   = NULL;
          char** newenvp    = NULL; /* current environment */
    #ifdef __solaris__
          char*  dmpath     = NULL;  /* data model specific LD_LIBRARY_PATH,
                                        Solaris only */
    #endif /* __solaris__ */
    #endif  /* SETENV_REQUIRED */
    
          char** newargv    = NULL;
          int    newargc    = 0;
    
          /*
           * Starting in 1.5, all unix platforms accept the -d32 and -d64
           * options.  On platforms where only one data-model is supported
           * (e.g. ia-64 Linux), using the flag for the other data model is
           * an error and will terminate the program.
           */
    
          { /* open new scope to declare local variables */
            int i;
    
            newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*));
            newargv[newargc++] = argv[0];
    
            /* scan for data model arguments and remove from argument list;
               last occurrence determines desired data model */
            for (i=1; i < argc; i++) {
    
              if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) {
                wanted = 64;
                continue;
              }
              if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) {
                wanted = 32;
                continue;
              }
              newargv[newargc++] = argv[i];
    
              if (IsJavaArgs()) {
                if (argv[i][0] != '-') continue;
              } else {
                if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) {
                  i++;
                  if (i >= argc) break;
                  newargv[newargc++] = argv[i];
                  continue;
                }
                if (argv[i][0] != '-') { i++; break; }
              }
            }
    
            /* copy rest of args [i .. argc) */
            while (i < argc) {
              newargv[newargc++] = argv[i++];
            }
            newargv[newargc] = NULL;
    
            /*
             * newargv has all proper arguments here
             */
    
            argc = newargc;
            argv = newargv;
          }
    
          /* If the data model is not changing, it is an error if the
             jvmpath does not exist */
          if (wanted == running) {
            /* Find out where the JRE is that we will be using. */
            if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {
              JLI_ReportErrorMessage(JRE_ERROR1);
              exit(2);
            }
            JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
                         jrepath, FILESEP, FILESEP,  arch, FILESEP);
            /* Find the specified JVM type */
            if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
              JLI_ReportErrorMessage(CFG_ERROR7);
              exit(1);
            }
    
            jvmpath[0] = '';
            jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
            if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
                JLI_ReportErrorMessage(CFG_ERROR9);
                exit(4);
            }
    
            if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, arch, 0 )) {
              JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
              exit(4);
            }
            /*
             * we seem to have everything we need, so without further ado
             * we return back, otherwise proceed to set the environment.
             */
    #ifdef SETENV_REQUIRED
            mustsetenv = RequiresSetenv(wanted, jvmpath);
            JLI_TraceLauncher("mustsetenv: %s
    ", mustsetenv ? "TRUE" : "FALSE");
    
            if (mustsetenv == JNI_FALSE) {
                JLI_MemFree(newargv);
                return;
            }
    #else
            JLI_MemFree(newargv);
            return;
    #endif /* SETENV_REQUIRED */
          } else {  /* do the same speculatively or exit */
    #ifdef DUAL_MODE
            if (running != wanted) {
              /* Find out where the JRE is that we will be using. */
              if (!GetJREPath(jrepath, so_jrepath, GetArchPath(wanted), JNI_TRUE)) {
                /* give up and let other code report error message */
                JLI_ReportErrorMessage(JRE_ERROR2, wanted);
                exit(1);
              }
              JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
                           jrepath, FILESEP, FILESEP, GetArchPath(wanted), FILESEP);
              /*
               * Read in jvm.cfg for target data model and process vm
               * selection options.
               */
              if (ReadKnownVMs(jvmcfg, JNI_TRUE) < 1) {
                /* give up and let other code report error message */
                JLI_ReportErrorMessage(JRE_ERROR2, wanted);
                exit(1);
              }
              jvmpath[0] = '';
              jvmtype = CheckJvmType(pargc, pargv, JNI_TRUE);
              if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
                JLI_ReportErrorMessage(CFG_ERROR9);
                exit(4);
              }
    
              /* exec child can do error checking on the existence of the path */
              jvmpathExists = GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, GetArchPath(wanted), 0);
    #ifdef SETENV_REQUIRED
              mustsetenv = RequiresSetenv(wanted, jvmpath);
    #endif /* SETENV_REQUIRED */
            }
    #else /* ! DUALMODE */
            JLI_ReportErrorMessage(JRE_ERROR2, wanted);
            exit(1);
    #endif /* DUAL_MODE */
            }
    #ifdef SETENV_REQUIRED
            if (mustsetenv) {
                /*
                 * We will set the LD_LIBRARY_PATH as follows:
                 *
                 *     o          $JVMPATH (directory portion only)
                 *     o          $JRE/lib/$LIBARCHNAME
                 *     o          $JRE/../lib/$LIBARCHNAME
                 *
                 * followed by the user's previous effective LD_LIBRARY_PATH, if
                 * any.
                 */
    
    #ifdef __solaris__
                /*
                 * Starting in Solaris 7, ld.so.1 supports three LD_LIBRARY_PATH
                 * variables:
                 *
                 * 1. LD_LIBRARY_PATH -- used for 32 and 64 bit searches if
                 * data-model specific variables are not set.
                 *
                 * 2. LD_LIBRARY_PATH_64 -- overrides and replaces LD_LIBRARY_PATH
                 * for 64-bit binaries.
                 *
                 * 3. LD_LIBRARY_PATH_32 -- overrides and replaces LD_LIBRARY_PATH
                 * for 32-bit binaries.
                 *
                 * The vm uses LD_LIBRARY_PATH to set the java.library.path system
                 * property.  To shield the vm from the complication of multiple
                 * LD_LIBRARY_PATH variables, if the appropriate data model
                 * specific variable is set, we will act as if LD_LIBRARY_PATH had
                 * the value of the data model specific variant and the data model
                 * specific variant will be unset.  Note that the variable for the
                 * *wanted* data model must be used (if it is set), not simply the
                 * current running data model.
                 */
    
                switch (wanted) {
                    case 0:
                        if (running == 32) {
                            dmpath = getenv("LD_LIBRARY_PATH_32");
                            wanted = 32;
                        } else {
                            dmpath = getenv("LD_LIBRARY_PATH_64");
                            wanted = 64;
                        }
                        break;
    
                    case 32:
                        dmpath = getenv("LD_LIBRARY_PATH_32");
                        break;
    
                    case 64:
                        dmpath = getenv("LD_LIBRARY_PATH_64");
                        break;
    
                    default:
                        JLI_ReportErrorMessage(JRE_ERROR3, __LINE__);
                        exit(1); /* unknown value in wanted */
                        break;
                }
    
                /*
                 * If dmpath is NULL, the relevant data model specific variable is
                 * not set and normal LD_LIBRARY_PATH should be used.
                 */
                if (dmpath == NULL) {
                    runpath = getenv("LD_LIBRARY_PATH");
                } else {
                    runpath = dmpath;
                }
    #else /* ! __solaris__ */
                /*
                 * If not on Solaris, assume only a single LD_LIBRARY_PATH
                 * variable.
                 */
                runpath = getenv("LD_LIBRARY_PATH");
    #endif /* __solaris__ */
    
                /* runpath contains current effective LD_LIBRARY_PATH setting */
    
                jvmpath = JLI_StringDup(jvmpath);
                new_runpath = JLI_MemAlloc(((runpath != NULL) ? JLI_StrLen(runpath) : 0) +
                        2 * JLI_StrLen(jrepath) + 2 * JLI_StrLen(arch) +
                        JLI_StrLen(jvmpath) + 52);
                newpath = new_runpath + JLI_StrLen("LD_LIBRARY_PATH=");
    
    
                /*
                 * Create desired LD_LIBRARY_PATH value for target data model.
                 */
                {
                    /* remove the name of the .so from the JVM path */
                    lastslash = JLI_StrRChr(jvmpath, '/');
                    if (lastslash)
                        *lastslash = '';
    
                    sprintf(new_runpath, "LD_LIBRARY_PATH="
                            "%s:"
                            "%s/lib/%s:"
                            "%s/../lib/%s",
                            jvmpath,
    #ifdef DUAL_MODE
                            jrepath, GetArchPath(wanted),
                            jrepath, GetArchPath(wanted)
    #else /* !DUAL_MODE */
                            jrepath, arch,
                            jrepath, arch
    #endif /* DUAL_MODE */
                            );
    
    
                    /*
                     * Check to make sure that the prefix of the current path is the
                     * desired environment variable setting, though the RequiresSetenv
                     * checks if the desired runpath exists, this logic does a more
                     * comprehensive check.
                     */
                    if (runpath != NULL &&
                            JLI_StrNCmp(newpath, runpath, JLI_StrLen(newpath)) == 0 &&
                            (runpath[JLI_StrLen(newpath)] == 0 || runpath[JLI_StrLen(newpath)] == ':') &&
                            (running == wanted) /* data model does not have to be changed */
    #ifdef __solaris__
                            && (dmpath == NULL) /* data model specific variables not set  */
    #endif /* __solaris__ */
                            ) {
                        JLI_MemFree(newargv);
                        JLI_MemFree(new_runpath);
                        return;
                    }
                }
    
                /*
                 * Place the desired environment setting onto the prefix of
                 * LD_LIBRARY_PATH.  Note that this prevents any possible infinite
                 * loop of execv() because we test for the prefix, above.
                 */
                if (runpath != 0) {
                    JLI_StrCat(new_runpath, ":");
                    JLI_StrCat(new_runpath, runpath);
                }
    
                if (putenv(new_runpath) != 0) {
                    exit(1); /* problem allocating memory; LD_LIBRARY_PATH not set
                        properly */
                }
    
                /*
                 * Unix systems document that they look at LD_LIBRARY_PATH only
                 * once at startup, so we have to re-exec the current executable
                 * to get the changed environment variable to have an effect.
                 */
    
    #ifdef __solaris__
                /*
                 * If dmpath is not NULL, remove the data model specific string
                 * in the environment for the exec'ed child.
                 */
                if (dmpath != NULL)
                    (void)UnsetEnv((wanted == 32) ? "LD_LIBRARY_PATH_32" : "LD_LIBRARY_PATH_64");
    #endif /* __solaris */
    
                newenvp = environ;
            }
    #endif /* SETENV_REQUIRED */
            {
                char *newexec = execname;
    #ifdef DUAL_MODE
                /*
                 * If the data model is being changed, the path to the
                 * executable must be updated accordingly; the executable name
                 * and directory the executable resides in are separate.  In the
                 * case of 32 => 64, the new bits are assumed to reside in, e.g.
                 * "olddir/LIBARCH64NAME/execname"; in the case of 64 => 32,
                 * the bits are assumed to be in "olddir/../execname".  For example,
                 *
                 * olddir/sparcv9/execname
                 * olddir/amd64/execname
                 *
                 * for Solaris SPARC and Linux amd64, respectively.
                 */
    
                if (running != wanted) {
                    char *oldexec = JLI_StrCpy(JLI_MemAlloc(JLI_StrLen(execname) + 1), execname);
                    char *olddir = oldexec;
                    char *oldbase = JLI_StrRChr(oldexec, '/');
    
    
                    newexec = JLI_MemAlloc(JLI_StrLen(execname) + 20);
                    *oldbase++ = 0;
                    sprintf(newexec, "%s/%s/%s", olddir,
                            ((wanted == 64) ? LIBARCH64NAME : ".."), oldbase);
                    argv[0] = newexec;
                }
    #endif /* DUAL_MODE */
                JLI_TraceLauncher("TRACER_MARKER:About to EXEC
    ");
                (void) fflush(stdout);
                (void) fflush(stderr);
    #ifdef SETENV_REQUIRED
                if (mustsetenv) {
                    execve(newexec, argv, newenvp);
                } else {
                    execv(newexec, argv);
                }
    #else /* !SETENV_REQUIRED */
                execv(newexec, argv);
    #endif /* SETENV_REQUIRED */
                JLI_ReportErrorMessageSys(JRE_ERROR4, newexec);
    
    #ifdef DUAL_MODE
                if (running != wanted) {
                    JLI_ReportErrorMessage(JRE_ERROR5, wanted, running);
    #ifdef __solaris__
    #ifdef __sparc
                    JLI_ReportErrorMessage(JRE_ERROR6);
    #else  /* ! __sparc__ */
                    JLI_ReportErrorMessage(JRE_ERROR7);
    #endif  /* __sparc */
    #endif /* __solaris__ */
                }
    #endif /* DUAL_MODE */
    
            }
            exit(1);
        }
    }
    View Code

    1.3. 装载jvm链接库

      经过前面的查找与验证,已经确认系统上有相应的jre环境了。但还没有进行真正的调用,这是重中之重。不过其实现却也是简单的,因为,它只是加载一个外部动态库而已。其主要目的在于获取与动态库的联系,直接些就是获取几个jvm的函数指针入口,以便后续可以调用。这和我们常说的面向接口编程,也一脉相承。

    // 咱们就只看linux版本的实现好了,原理都一样,各平台实现不同而已(API规范不同)
    // solaris/bin/java_md_solinux.c
    jboolean
    LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
    {
        void *libjvm;
    
        JLI_TraceLauncher("JVM path is %s
    ", jvmpath);
        // jvmpath 是在前面解析出的地址, 直接加载打开即可获得
        libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
        if (libjvm == NULL) {
    #if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
          FILE * fp;
          Elf32_Ehdr elf_head;
          int count;
          int location;
    
          fp = fopen(jvmpath, "r");
          if (fp == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
          }
    
          /* read in elf header */
          count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
          fclose(fp);
          if (count < 1) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
          }
    
          /*
           * Check for running a server vm (compiled with -xarch=v8plus)
           * on a stock v8 processor.  In this case, the machine type in
           * the elf header would not be included the architecture list
           * provided by the isalist command, which is turn is gotten from
           * sysinfo.  This case cannot occur on 64-bit hardware and thus
           * does not have to be checked for in binaries with an LP64 data
           * model.
           */
          if (elf_head.e_machine == EM_SPARC32PLUS) {
            char buf[257];  /* recommended buffer size from sysinfo man
                               page */
            long length;
            char* location;
    
            length = sysinfo(SI_ISALIST, buf, 257);
            if (length > 0) {
                location = JLI_StrStr(buf, "sparcv8plus ");
              if (location == NULL) {
                JLI_ReportErrorMessage(JVM_ERROR3);
                return JNI_FALSE;
              }
            }
          }
    #endif
            JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
        // 加载jvm的目的,主要就是为了获取 JNI_CreateJavaVM, 
        //         JNI_GetDefaultJavaVMInitArgs, JNI_GetCreatedJavaVMs 这些个指针
        ifn->CreateJavaVM = (CreateJavaVM_t)
            dlsym(libjvm, "JNI_CreateJavaVM");
        if (ifn->CreateJavaVM == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
            dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
        if (ifn->GetDefaultJavaVMInitArgs == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
            dlsym(libjvm, "JNI_GetCreatedJavaVMs");
        if (ifn->GetCreatedJavaVMs == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        return JNI_TRUE;
    }

      可见装载jvm的过程显得很清晰明了,因为并没有做真正的调用,所以也只是算是处理初始化阶段。有简单的系统api提供,一切都很轻量级。

      而jvm的真正创建,是在进行JvmInit()时,准备加载 main_class 时,才进行的的。

    1.4. 回顾JavaMain执行框架

      JavaMain是真正接入java代码的地方,它一般是会开启一个新线程去执行。前置调用可自行展开。

    // 只看在linux中的实现
    // solaris/bin/java_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);
    }
    // java.c
    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;
          // 传入 JavaMain, 在新线程中调用
          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;
        }
    }
    
    // solaris/bin/java_md_solinux.c
    /*
     * Block current thread and continue execution in a new thread
     */
    int
    ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
        int rslt;
    #ifdef __linux__
        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);
        }
        // 常见的 pthread_xx 方式 创建线程
        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);
    #else /* ! __linux__ */
        thread_t tid;
        long flags = 0;
        if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
          void * tmp;
          thr_join(tid, NULL, &tmp);
          rslt = (int)tmp;
        } else {
          /* See above. Continue in current thread if thr_create() failed */
          rslt = continuation(args);
        }
    #endif /* __linux__ */
        return rslt;
    }
    View Code

      JavaMain() 是执行java代码或者创建jvm的核心入口。

    // share/bin/java.c
    // 加载 main 函数类
    // 通过引入 JavaMain(), 接入java方法
    // #define JNICALL __stdcall
    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;
        // 一些jvm的调用实例,在之前的步骤中,通过加载相应动态链接方法,保存起来的
        /** 
         * ifn->CreateJavaVM =
         *   (void *)GetProcAddress(handle, "JNI_CreateJavaVM");
         * ifn->GetDefaultJavaVMInitArgs =
         *   (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");
         */
        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;
        // collector
        RegisterThread();
        /* Initialize the virtual machine */
        start = CounterGet();
        // 重点1:初始化jvm,失败则退出
        // 此处会将重要变量 *env 进程初始化,从而使后续可用
        if (!InitializeJVM(&vm, &env, &ifn)) {
            JLI_ReportErrorMessage(JVM_ERROR1);
            exit(1);
        }
        // jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvm
        if (showSettings != NULL) {
            ShowSettings(env, showSettings);
            /**
             * 宏是神奇的操作,此处 *env 直接引用
    #define CHECK_EXCEPTION_LEAVE(CEL_return_value) 
        do { 
            if ((*env)->ExceptionOccurred(env)) { 
                JLI_ReportExceptionDescription(env); 
                ret = (CEL_return_value); 
                LEAVE(); 
            } 
        } while (JNI_FALSE)
             */
            CHECK_EXCEPTION_LEAVE(1);
        }
        // 调用 LEAVE() 方法的目的在于主动销毁jvm线程
        // 且退出当前方法调用,即 LEAVE() 后方法不再被执行
    /*
     * Always 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.
     *
     * 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.
     */
        /**
         *
         * 
    #define LEAVE() 
        do { 
            if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { 
                JLI_ReportErrorMessage(JVM_ERROR2); 
                ret = 1; 
            } 
            if (JNI_TRUE) { 
                (*vm)->DestroyJavaVM(vm); 
                return ret; 
            } 
        } while (JNI_FALSE)
         */
        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
    ",
                   (long)(jint)Counter2Micros(end-start));
        }
        /* At this stage, argc/argv have the application's arguments */
        if (JLI_IsTraceLauncher()){
            int i;
            printf("%s is '%s'
    ", launchModeNames[mode], what);
            printf("App's argc is %d
    ", argc);
            for (i=0; i < argc; i++) {
                printf("    argv[%2d] = '%s'
    ", 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.
         */
        // 重点2:加载 main 指定的class类
        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.
         */
        // 加载main() 方法前执行初始化
        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.
         */
        // 重点3:执行 main(args[]) java方法
        // 获取main()方法id, main(String[] args)
        mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                           "([Ljava/lang/String;)V");
        CHECK_EXCEPTION_NULL_LEAVE(mainID);
        /* Build platform specific argument array */
        // 构建args[] 参数
        mainArgs = CreateApplicationArgs(env, argv, argc);
        CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
        /* Invoke main method. */
        // 调用java实现的main()方法
        // XX:: 重要实现
        (*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();
    }
    /*
     * Loads a class and verifies that the main class is present and it is ok to
     * call it for more details refer to the java implementation.
     */
    static jclass
    LoadMainClass(JNIEnv *env, int mode, char *name)
    {
        jmethodID mid;
        jstring str;
        jobject result;
        jlong start, end;
        // sun/launcher/LauncherHelper
        jclass cls = GetLauncherHelperClass(env);
        NULL_CHECK0(cls);
        if (JLI_IsTraceLauncher()) {
            start = CounterGet();
        }
        // checkAndLoadMain(String) 方法作为中间main()调用
        NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                    "checkAndLoadMain",
                    "(ZILjava/lang/String;)Ljava/lang/Class;"));
        str = NewPlatformString(env, name);
        CHECK_JNI_RETURN_0(
            result = (*env)->CallStaticObjectMethod(
                env, cls, mid, USE_STDERR, mode, str));
        if (JLI_IsTraceLauncher()) {
            end   = CounterGet();
            printf("%ld micro seconds to load main class
    ",
                   (long)(jint)Counter2Micros(end-start));
            printf("----%s----
    ", JLDEBUG_ENV_ENTRY);
        }
        return (jclass)result;
    }    
    jclass
    GetLauncherHelperClass(JNIEnv *env)
    {
        if (helperClass == NULL) {
            NULL_CHECK0(helperClass = FindBootStrapClass(env,
                    "sun/launcher/LauncherHelper"));
        }
        return helperClass;
    }

      JavaMain 框架大概就是初始化创建jvm, 查找mainClass类, 找到函数指定, 构建args参数, 执行main(), 以及其他的一些兼容性处理。当JavaMain  执行完成时,则意味着整个jvm就完成了。所以,这也成为了我们要研究的重中之重。

    2. 真正的jvm创建

      jdk是我们看到的jvm前端,而背后的jre或者jvm才是大佬。它是在 JavaMain() 中触发调用的。也就是上节中看到的框架结构。初始化JVM的过程,实际就是调用jvm的函数指针 JNI_CreateJavaVM 地过程。

    // 初始化jvm, 主要是调用 CreateJavaVM() 方法,进行创建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:
        ");
            printf("version 0x%08lx, ", (long)args.version);
            printf("ignoreUnrecognized is %s, ",
                   args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
            printf("nOptions is %ld
    ", (long)args.nOptions);
            for (i = 0; i < numOptions; i++)
                printf("    option[%2d] = '%s'
    ",
                       i, args.options[i].optionString);
        }
        // 转交给jvm执行
        r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
        JLI_MemFree(options);
        return r == JNI_OK;
    }

      单是这 CreateJavaVM(), 就将jvm接入进来了。它的作用,就像是很多语言的 main() 入口一样,看似简单,却包罗万象。

    2.1. jni.h文件概述

      jni.h 中定义了许多的jdk可以调用的方法。比如上面提到 JNI_CreateJavaVM() 就是创建jvm的核心入口。在openjdk中,是在hotspot中实现的。

      其中定义了各种java的类型,各种需要的接口。当我们想要自定义写一些native接口时,则jni.h是我们必须要引入的。其开头部分如下:

    // share/vm/prims/jni.h
    /*
     * We used part of Netscape's Java Runtime Interface (JRI) as the starting
     * point of our design and implementation.
     */
    
    /******************************************************************************
     * Java Runtime Interface
     * Copyright (c) 1996 Netscape Communications Corporation. All rights reserved.
     *****************************************************************************/
    
    #ifndef _JAVASOFT_JNI_H_
    #define _JAVASOFT_JNI_H_
    
    #include <stdio.h>
    #include <stdarg.h>
    
    /* jni_md.h contains the machine-dependent typedefs for jbyte, jint
       and jlong */
    
    #include "jni_md.h"
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /*
     * JNI Types
     */
    
    #ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
    
    typedef unsigned char   jboolean;
    typedef unsigned short  jchar;
    typedef short           jshort;
    typedef float           jfloat;
    typedef double          jdouble;
    
    typedef jint            jsize;
    
    #ifdef __cplusplus
    // c++版本的对象定义
    class _jobject {};
    class _jclass : public _jobject {};
    class _jthrowable : public _jobject {};
    class _jstring : public _jobject {};
    class _jarray : public _jobject {};
    class _jbooleanArray : public _jarray {};
    class _jbyteArray : public _jarray {};
    class _jcharArray : public _jarray {};
    class _jshortArray : public _jarray {};
    class _jintArray : public _jarray {};
    class _jlongArray : public _jarray {};
    class _jfloatArray : public _jarray {};
    class _jdoubleArray : public _jarray {};
    class _jobjectArray : public _jarray {};
    
    typedef _jobject *jobject;
    typedef _jclass *jclass;
    typedef _jthrowable *jthrowable;
    typedef _jstring *jstring;
    typedef _jarray *jarray;
    typedef _jbooleanArray *jbooleanArray;
    typedef _jbyteArray *jbyteArray;
    typedef _jcharArray *jcharArray;
    typedef _jshortArray *jshortArray;
    typedef _jintArray *jintArray;
    typedef _jlongArray *jlongArray;
    typedef _jfloatArray *jfloatArray;
    typedef _jdoubleArray *jdoubleArray;
    typedef _jobjectArray *jobjectArray;
    
    #else
    // c版本的对象定义
    struct _jobject;
    
    typedef struct _jobject *jobject;
    typedef jobject jclass;
    typedef jobject jthrowable;
    typedef jobject jstring;
    typedef jobject jarray;
    typedef jarray jbooleanArray;
    typedef jarray jbyteArray;
    typedef jarray jcharArray;
    typedef jarray jshortArray;
    typedef jarray jintArray;
    typedef jarray jlongArray;
    typedef jarray jfloatArray;
    typedef jarray jdoubleArray;
    typedef jarray jobjectArray;
    
    #endif
    
    typedef jobject jweak;
    // java各类型的定义简写
    typedef union jvalue {
        jboolean z;
        jbyte    b;
        jchar    c;
        jshort   s;
        jint     i;
        jlong    j;
        jfloat   f;
        jdouble  d;
        jobject  l;
    } jvalue;
    
    struct _jfieldID;
    typedef struct _jfieldID *jfieldID;
    
    struct _jmethodID;
    typedef struct _jmethodID *jmethodID;
    
    /* Return values from jobjectRefType */
    typedef enum _jobjectType {
         JNIInvalidRefType    = 0,
         JNILocalRefType      = 1,
         JNIGlobalRefType     = 2,
         JNIWeakGlobalRefType = 3
    } jobjectRefType;
    
    
    #endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */
    
    /*
     * jboolean constants
     */
    
    #define JNI_FALSE 0
    #define JNI_TRUE 1
    
    /*
     * possible return values for JNI functions.
     */
    
    #define JNI_OK           0                 /* success */
    #define JNI_ERR          (-1)              /* unknown error */
    #define JNI_EDETACHED    (-2)              /* thread detached from the VM */
    #define JNI_EVERSION     (-3)              /* JNI version error */
    #define JNI_ENOMEM       (-4)              /* not enough memory */
    #define JNI_EEXIST       (-5)              /* VM already created */
    #define JNI_EINVAL       (-6)              /* invalid arguments */
    
    /*
     * used in ReleaseScalarArrayElements
     */
    
    #define JNI_COMMIT 1
    #define JNI_ABORT 2
    
    /*
     * used in RegisterNatives to describe native method name, signature,
     * and function pointer.
     */
    
    typedef struct {
        char *name;
        char *signature;
        void *fnPtr;
    } JNINativeMethod;
    ...

      大概就是兼容各平台,可能使用C实现,也可能使用C++实现。完整版本请参考官网: http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/6ea5a8067d1f/src/share/vm/prims/jni.h

      其中,有两个比较重的结构体的定义:JNINativeInterface_ 是jni调用的大部分接口定义,基本上可以通过它调用任意方法。JNIEnv_ 是每个方法调用时的上下文管理器,它负责调用 JNINativeInterface_ 的方法,相当于是C++版本的JNINativeInterface_。

    2.3. jvm核心创建框架

      在jni.h中,还有很多C++或者C的判断,但对于CreateJavaVM这件事,就变成了一个纯粹C++的实现了。

    // hotspot/src/share/vm/prims/jni.cpp
    _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)
      {
        // java 魔术头
        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");
    
      /**
       * Certain errors during initialization are recoverable and do not
       * prevent this method from being called again at a later time
       * (perhaps with different arguments).  However, at a certain
       * point during initialization if an error occurs we cannot allow
       * this function to be called again (or it will crash).  In those
       * situations, the 'canTryAgain' flag is set to false, which atomically
       * sets safe_to_recreate_vm to 1, such that any new call to
       * JNI_CreateJavaVM will immediately fail using the above logic.
       */
      bool can_try_again = true;
      // 核心: 创建vm
      result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
      if (result == JNI_OK) {
        JavaThread *thread = JavaThread::current();
        /* thread is thread_in_vm here */
        *vm = (JavaVM *)(&main_vm);
        // 将jvm信息存储到 penv 中,以备外部使用
        *(JNIEnv**)penv = thread->jni_environment();
    
        // Tracks the time application was running before GC
        RuntimeService::record_application_start();
    
        // Notify 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
    
        // Check if we should compile all classes on bootclasspath
        if (CompileTheWorld) ClassLoader::compile_the_world();
        if (ReplayCompiles) ciReplay::replay(thread);
    
        // Some platforms (like Win*) need a wrapper around these test
        // functions in order to properly handle error conditions.
        CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
        CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
    #endif
    
        // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
        ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
      } else {
        // 创建VM失败, 还原标识位信息
        if (can_try_again) {
          // reset safe_to_recreate_vm to 1 so that retrial would be possible
          safe_to_recreate_vm = 1;
        }
    
        // Creation failed. We must reset vm_created
        *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;
    }

      看C++代码果然有点费劲,不过有着注释的加持,还算可以理解。大致就是测试环境,然后上CAS锁,保证vm加载时的线程安全性,进行vm创建,然后将vm的环境信息赋值给 外部 penv, 测试下vm有效性, 返回创建状态。

      核心仍然是被包裹着的:Threads::create_vm()

    // share/vm/runtime/thread.cpp
    jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
    
      extern void JDK_Version_init();
    
      // Check version
      if (!is_supported_jni_version(args->version)) return JNI_EVERSION;
    
      // Initialize the output stream module
      ostream_init();
    
      // Process java launcher properties.
      Arguments::process_sun_java_launcher_properties(args);
    
      // Initialize the os module before using TLS
      os::init();
    
      // Initialize system properties.
      Arguments::init_system_properties();
    
      // So that JDK version can be used as a discrimintor when parsing arguments
      JDK_Version_init();
    
      // Update/Initialize System properties after JDK version number is known
      Arguments::init_version_specific_system_properties();
    
      // Parse arguments
      jint parse_result = Arguments::parse(args);
      if (parse_result != JNI_OK) return parse_result;
    
      os::init_before_ergo();
    
      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
      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
      jint os_init_2_result = os::init_2();
      if (os_init_2_result != JNI_OK) return os_init_2_result;
    
      jint adjust_after_os_result = Arguments::adjust_after_os();
      if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;
    
      // intialize TLS
      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.
      MemTracker::bootstrap_single_thread();
    
      // Initialize output stream logging
      ostream_init_log();
    
      // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
      // Must be before create_vm_init_agents()
      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
      vm_init_globals();
    
      // Attach the main thread to this os thread
      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();
    
      main_thread->set_active_handles(JNIHandleBlock::allocate_block());
    
      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());
        initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
        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;
    }

      以上,就是vm创建的框架代码,也已经这么复杂了。大体有这么几个步骤:

    • 1. 检查jdk版本号, 不支持则退出;
    • 2. 输出流初始化;
    • 3. sun.java.launcher属性配置检查接入;
    • 4. 初始化一些系统模块,如随机数...;
    • 5. 初始化系统属性如java.ext.dirs...;
    • 6. 参数解析;
    • 7. 系统页初始化;
    • 8. 再初始化更多平台相关的系统模块, 如线程,页设置,mmap,PV机制,maxfd,优先级等;
    • 9. ThreadLocalStorage初始化;
    • 10. 内存跟踪器初始化;
    • 11. agentlib 初始化;
    • 12. 全局变量初始化;
    • 13. 创建JavaThread, 初始化信息;
    • 14. 将java线程映射到系统线程JavaThread -> OSThread;
    • 15. ObjectMonitor对象监视器创建初始化;
    • 16. 进入多线程模式;
    • 17. 初始化java的全局模块,如bytecode,classloader...;
    • 18. 添加main_thread到线程表中;
    • 19. 创建 VMThread 线程, 启动vmThread线程并等待其事务处理完成;
    • 20. JvmtiExport开始执行, 保证agent开始切入;
    • 21. 初始化java系统类库,如system,string...;
    • 22. 初始化编译器compiler;
    • 23. jni函数信息设置;
    • 24. jdwp调试模块运行;
    • 25. AttachListener启动运行;
    • 26. BiasedLocking 初始化;
    • 27. WatcherThread 启动;
    • 28. PeriodicTask 任务运行;
    • 29. 执行完成, 返回创建成功;

       

      细节就不说了(哈哈,因为说也说不清楚)。 我们只需理解流程即可,真正想理解,那么就需要指定一个特定的小点,来进行探讨了。以后再说咯!

    3. 核心变量JNIEnv 的前世今生

      在jni.h中,JNINativeInterface_ 定义了许多的操作函数接口,即很多java的调用,都会调用这些方法。而这里面都有一个统一的第一个参数:JNIEnv* env 。可见其重要性。那么,这个变量又是如何初始化和创建的呢?既然是函数接口,那么必然需要具体的实现,这是具体在哪里定义的呢?

      实际上,我们可以通过前面对 JNIEnv **penv 的赋值中查到端倪:

        // hotspot/src/share/vm/prims/jni.cpp
        ...
        // 将jvm信息存储到 penv 中,以备外部使用
        *(JNIEnv**)penv = thread->jni_environment();
        ...
        // 而查看 jni_environment() 方法可知,其由一个类变量 _jni_environment 处理
      // share/vm/runtime/thread.hpp
      // Returns the jni environment for this thread
      JNIEnv* jni_environment()                      { return &_jni_environment; }

      所以,我们只需找出 _jni_environment 是如何赋值初始化,即可知道如何获取这个关键变量的逻辑了。结果是,在创建JavaThread, 在进行初始化时,便会设置该值。

    // share/vm/runtime/thread.cpp
    JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
      Thread()
    #if INCLUDE_ALL_GCS
      , _satb_mark_queue(&_satb_mark_queue_set),
      _dirty_card_queue(&_dirty_card_queue_set)
    #endif // INCLUDE_ALL_GCS
    {
      if (TraceThreadEvents) {
        tty->print_cr("creating thread %p", this);
      }
      // 初始化线程变量信息, 如 JNIEnv
      initialize();
      _jni_attach_state = _not_attaching_via_jni;
      set_entry_point(entry_point);
      // Create the native thread itself.
      // %note runtime_23
      os::ThreadType thr_type = os::java_thread;
      thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                         os::java_thread;
      os::create_thread(this, thr_type, stack_sz);
      _safepoint_visible = false;
      // The _osthread may be NULL here because we ran out of memory (too many threads active).
      // We need to throw and OutOfMemoryError - however we cannot do this here because the caller
      // may hold a lock and all locks must be unlocked before throwing the exception (throwing
      // the exception consists of creating the exception object & initializing it, initialization
      // will leave the VM via a JavaCall and then all locks must be unlocked).
      //
      // The thread is still suspended when we reach here. Thread must be explicit started
      // by creator! Furthermore, the thread must also explicitly be added to the Threads list
      // by calling Threads:add. The reason why this is not done here, is because the thread
      // object must be fully initialized (take a look at JVM_Start)
    }
    
    // A JavaThread is a normal Java thread
    void JavaThread::initialize() {
      // Initialize fields
    
      // Set the claimed par_id to -1 (ie not claiming any par_ids)
      set_claimed_par_id(-1);
    
      set_saved_exception_pc(NULL);
      set_threadObj(NULL);
      _anchor.clear();
      set_entry_point(NULL);
      // 取数jni_functions, 初始化到 _jni_environment
      set_jni_functions(jni_functions());
      set_callee_target(NULL);
      set_vm_result(NULL);
      set_vm_result_2(NULL);
      set_vframe_array_head(NULL);
      set_vframe_array_last(NULL);
      set_deferred_locals(NULL);
      set_deopt_mark(NULL);
      set_deopt_nmethod(NULL);
      clear_must_deopt_id();
      set_monitor_chunks(NULL);
      set_next(NULL);
      set_thread_state(_thread_new);
    #if INCLUDE_NMT
      set_recorder(NULL);
    #endif
      _terminated = _not_terminated;
      _privileged_stack_top = NULL;
      _array_for_gc = NULL;
      _suspend_equivalent = false;
      _in_deopt_handler = 0;
      _doing_unsafe_access = false;
      _stack_guard_state = stack_guard_unused;
      (void)const_cast<oop&>(_exception_oop = NULL);
      _exception_pc  = 0;
      _exception_handler_pc = 0;
      _is_method_handle_return = 0;
      _jvmti_thread_state= NULL;
      _should_post_on_exceptions_flag = JNI_FALSE;
      _jvmti_get_loaded_classes_closure = NULL;
      _interp_only_mode    = 0;
      _special_runtime_exit_condition = _no_async_condition;
      _pending_async_exception = NULL;
      _thread_stat = NULL;
      _thread_stat = new ThreadStatistics();
      _blocked_on_compilation = false;
      _jni_active_critical = 0;
      _do_not_unlock_if_synchronized = false;
      _cached_monitor_info = NULL;
      _parker = Parker::Allocate(this) ;
    
    #ifndef PRODUCT
      _jmp_ring_index = 0;
      for (int ji = 0 ; ji < jump_ring_buffer_size ; ji++ ) {
        record_jump(NULL, NULL, NULL, 0);
      }
    #endif /* PRODUCT */
    
      set_thread_profiler(NULL);
      if (FlatProfiler::is_active()) {
        // This is where we would decide to either give each thread it's own profiler
        // or use one global one from FlatProfiler,
        // or up to some count of the number of profiled threads, etc.
        ThreadProfiler* pp = new ThreadProfiler();
        pp->engage();
        set_thread_profiler(pp);
      }
    
      // Setup safepoint state info for this thread
      ThreadSafepointState::create(this);
    
      debug_only(_java_call_counter = 0);
    
      // JVMTI PopFrame support
      _popframe_condition = popframe_inactive;
      _popframe_preserved_args = NULL;
      _popframe_preserved_args_size = 0;
    
      pd_initialize();
    }
        
    // Returns the function structure
    struct JNINativeInterface_* jni_functions() {
    #if INCLUDE_JNI_CHECK
      if (CheckJNICalls) return jni_functions_check();
    #endif // INCLUDE_JNI_CHECK
      return &jni_NativeInterface;
    }
      // thread.hpp
      //JNI functiontable getter/setter for JVMTI jni function table interception API.
      void set_jni_functions(struct JNINativeInterface_* functionTable) {
        _jni_environment.functions = functionTable;
      }

      所以,核心的初始化变成了 jni_NativeInterface 的具体值问题了。不过幸好,这是一个被定义为全局变量的,不至于被迷惑了。一看便知具体有哪些实现了。

    // jni.cpp
    // Structure containing all jni functions
    struct JNINativeInterface_ jni_NativeInterface = {
        NULL,
        NULL,
        NULL,
    
        NULL,
    
        jni_GetVersion,
    
        jni_DefineClass,
        jni_FindClass,
    
        jni_FromReflectedMethod,
        jni_FromReflectedField,
    
        jni_ToReflectedMethod,
    
        jni_GetSuperclass,
        jni_IsAssignableFrom,
    
        jni_ToReflectedField,
    
        jni_Throw,
        jni_ThrowNew,
        jni_ExceptionOccurred,
        jni_ExceptionDescribe,
        jni_ExceptionClear,
        jni_FatalError,
    
        jni_PushLocalFrame,
        jni_PopLocalFrame,
    
        jni_NewGlobalRef,
        jni_DeleteGlobalRef,
        jni_DeleteLocalRef,
        jni_IsSameObject,
    
        jni_NewLocalRef,
        jni_EnsureLocalCapacity,
    
        jni_AllocObject,
        jni_NewObject,
        jni_NewObjectV,
        jni_NewObjectA,
    
        jni_GetObjectClass,
        jni_IsInstanceOf,
    
        jni_GetMethodID,
    
        jni_CallObjectMethod,
        jni_CallObjectMethodV,
        jni_CallObjectMethodA,
        jni_CallBooleanMethod,
        jni_CallBooleanMethodV,
        jni_CallBooleanMethodA,
        jni_CallByteMethod,
        jni_CallByteMethodV,
        jni_CallByteMethodA,
        jni_CallCharMethod,
        jni_CallCharMethodV,
        jni_CallCharMethodA,
        jni_CallShortMethod,
        jni_CallShortMethodV,
        jni_CallShortMethodA,
        jni_CallIntMethod,
        jni_CallIntMethodV,
        jni_CallIntMethodA,
        jni_CallLongMethod,
        jni_CallLongMethodV,
        jni_CallLongMethodA,
        jni_CallFloatMethod,
        jni_CallFloatMethodV,
        jni_CallFloatMethodA,
        jni_CallDoubleMethod,
        jni_CallDoubleMethodV,
        jni_CallDoubleMethodA,
        jni_CallVoidMethod,
        jni_CallVoidMethodV,
        jni_CallVoidMethodA,
    
        jni_CallNonvirtualObjectMethod,
        jni_CallNonvirtualObjectMethodV,
        jni_CallNonvirtualObjectMethodA,
        jni_CallNonvirtualBooleanMethod,
        jni_CallNonvirtualBooleanMethodV,
        jni_CallNonvirtualBooleanMethodA,
        jni_CallNonvirtualByteMethod,
        jni_CallNonvirtualByteMethodV,
        jni_CallNonvirtualByteMethodA,
        jni_CallNonvirtualCharMethod,
        jni_CallNonvirtualCharMethodV,
        jni_CallNonvirtualCharMethodA,
        jni_CallNonvirtualShortMethod,
        jni_CallNonvirtualShortMethodV,
        jni_CallNonvirtualShortMethodA,
        jni_CallNonvirtualIntMethod,
        jni_CallNonvirtualIntMethodV,
        jni_CallNonvirtualIntMethodA,
        jni_CallNonvirtualLongMethod,
        jni_CallNonvirtualLongMethodV,
        jni_CallNonvirtualLongMethodA,
        jni_CallNonvirtualFloatMethod,
        jni_CallNonvirtualFloatMethodV,
        jni_CallNonvirtualFloatMethodA,
        jni_CallNonvirtualDoubleMethod,
        jni_CallNonvirtualDoubleMethodV,
        jni_CallNonvirtualDoubleMethodA,
        jni_CallNonvirtualVoidMethod,
        jni_CallNonvirtualVoidMethodV,
        jni_CallNonvirtualVoidMethodA,
    
        jni_GetFieldID,
    
        jni_GetObjectField,
        jni_GetBooleanField,
        jni_GetByteField,
        jni_GetCharField,
        jni_GetShortField,
        jni_GetIntField,
        jni_GetLongField,
        jni_GetFloatField,
        jni_GetDoubleField,
    
        jni_SetObjectField,
        jni_SetBooleanField,
        jni_SetByteField,
        jni_SetCharField,
        jni_SetShortField,
        jni_SetIntField,
        jni_SetLongField,
        jni_SetFloatField,
        jni_SetDoubleField,
    
        jni_GetStaticMethodID,
    
        jni_CallStaticObjectMethod,
        jni_CallStaticObjectMethodV,
        jni_CallStaticObjectMethodA,
        jni_CallStaticBooleanMethod,
        jni_CallStaticBooleanMethodV,
        jni_CallStaticBooleanMethodA,
        jni_CallStaticByteMethod,
        jni_CallStaticByteMethodV,
        jni_CallStaticByteMethodA,
        jni_CallStaticCharMethod,
        jni_CallStaticCharMethodV,
        jni_CallStaticCharMethodA,
        jni_CallStaticShortMethod,
        jni_CallStaticShortMethodV,
        jni_CallStaticShortMethodA,
        jni_CallStaticIntMethod,
        jni_CallStaticIntMethodV,
        jni_CallStaticIntMethodA,
        jni_CallStaticLongMethod,
        jni_CallStaticLongMethodV,
        jni_CallStaticLongMethodA,
        jni_CallStaticFloatMethod,
        jni_CallStaticFloatMethodV,
        jni_CallStaticFloatMethodA,
        jni_CallStaticDoubleMethod,
        jni_CallStaticDoubleMethodV,
        jni_CallStaticDoubleMethodA,
        jni_CallStaticVoidMethod,
        jni_CallStaticVoidMethodV,
        jni_CallStaticVoidMethodA,
    
        jni_GetStaticFieldID,
    
        jni_GetStaticObjectField,
        jni_GetStaticBooleanField,
        jni_GetStaticByteField,
        jni_GetStaticCharField,
        jni_GetStaticShortField,
        jni_GetStaticIntField,
        jni_GetStaticLongField,
        jni_GetStaticFloatField,
        jni_GetStaticDoubleField,
    
        jni_SetStaticObjectField,
        jni_SetStaticBooleanField,
        jni_SetStaticByteField,
        jni_SetStaticCharField,
        jni_SetStaticShortField,
        jni_SetStaticIntField,
        jni_SetStaticLongField,
        jni_SetStaticFloatField,
        jni_SetStaticDoubleField,
    
        jni_NewString,
        jni_GetStringLength,
        jni_GetStringChars,
        jni_ReleaseStringChars,
    
        jni_NewStringUTF,
        jni_GetStringUTFLength,
        jni_GetStringUTFChars,
        jni_ReleaseStringUTFChars,
    
        jni_GetArrayLength,
    
        jni_NewObjectArray,
        jni_GetObjectArrayElement,
        jni_SetObjectArrayElement,
    
        jni_NewBooleanArray,
        jni_NewByteArray,
        jni_NewCharArray,
        jni_NewShortArray,
        jni_NewIntArray,
        jni_NewLongArray,
        jni_NewFloatArray,
        jni_NewDoubleArray,
    
        jni_GetBooleanArrayElements,
        jni_GetByteArrayElements,
        jni_GetCharArrayElements,
        jni_GetShortArrayElements,
        jni_GetIntArrayElements,
        jni_GetLongArrayElements,
        jni_GetFloatArrayElements,
        jni_GetDoubleArrayElements,
    
        jni_ReleaseBooleanArrayElements,
        jni_ReleaseByteArrayElements,
        jni_ReleaseCharArrayElements,
        jni_ReleaseShortArrayElements,
        jni_ReleaseIntArrayElements,
        jni_ReleaseLongArrayElements,
        jni_ReleaseFloatArrayElements,
        jni_ReleaseDoubleArrayElements,
    
        jni_GetBooleanArrayRegion,
        jni_GetByteArrayRegion,
        jni_GetCharArrayRegion,
        jni_GetShortArrayRegion,
        jni_GetIntArrayRegion,
        jni_GetLongArrayRegion,
        jni_GetFloatArrayRegion,
        jni_GetDoubleArrayRegion,
    
        jni_SetBooleanArrayRegion,
        jni_SetByteArrayRegion,
        jni_SetCharArrayRegion,
        jni_SetShortArrayRegion,
        jni_SetIntArrayRegion,
        jni_SetLongArrayRegion,
        jni_SetFloatArrayRegion,
        jni_SetDoubleArrayRegion,
    
        jni_RegisterNatives,
        jni_UnregisterNatives,
    
        jni_MonitorEnter,
        jni_MonitorExit,
    
        jni_GetJavaVM,
    
        jni_GetStringRegion,
        jni_GetStringUTFRegion,
    
        jni_GetPrimitiveArrayCritical,
        jni_ReleasePrimitiveArrayCritical,
    
        jni_GetStringCritical,
        jni_ReleaseStringCritical,
    
        jni_NewWeakGlobalRef,
        jni_DeleteWeakGlobalRef,
    
        jni_ExceptionCheck,
    
        jni_NewDirectByteBuffer,
        jni_GetDirectBufferAddress,
        jni_GetDirectBufferCapacity,
    
        // New 1_6 features
    
        jni_GetObjectRefType
    };

      以上就是 JNIEnv* env 变量的设值过程了,它借助于java线程的创建时机进行初始化。而后续的使用中,几乎都会仰仗它来运行,可见其重要性。至于具体的实现,且听后续分解。

    不要害怕今日的苦,你要相信明天,更苦!
  • 相关阅读:
    maptalks 开发GIS地图(16)maptalks.three.09
    maptalks 开发GIS地图(15)maptalks.three.08
    maptalks 开发GIS地图(14)maptalks.three.07
    maptalks 开发GIS地图(12)maptalks.three.05 bar-music
    Use mongoose-CRUD operations
    First Mongoose Model
    MongoDB-1-CRUD Operations
    Mongo DB-0
    Kill port process
    Defining RESTful Routes(CRUD operations)
  • 原文地址:https://www.cnblogs.com/yougewe/p/14406217.html
Copyright © 2011-2022 走看看