JVM系列(三):JVM创建过程解析

JAVA烂猪皮

共 82056字,需浏览 165分钟

 · 2021-02-24

走过路过不要错过

点击蓝字关注我们


上两篇中梳理了整个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. */intJLI_Launch(int argc, char ** argv,              /* main argc, argc */        int jargc, const char** jargv,          /* java args */        int appclassc, const char** appclassv,  /* app classpath */        const char* fullversion,                /* full version defined */        const char* dotversion,                 /* dot version defined */        const char* pname,                      /* program name */        const char* lname,                      /* launcher name */        jboolean javaargs,                      /* JAVA_ARGS */        jboolean cpwildcard,                    /* classpath wildcard*/        jboolean javaw,                         /* windows-only javaw */        jint ergo                               /* ergonomics class policy */){    int mode = LM_UNKNOWN;    char *what = NULL;    char *cpath = 0;    char *main_class = NULL;    int ret;    InvocationFunctions ifn;    jlong start, end;    char jvmpath[MAXPATHLEN];    char jrepath[MAXPATHLEN];    char jvmcfg[MAXPATHLEN];    _fVersion = fullversion;    _dVersion = dotversion;    _launcher_name = lname;    _program_name = pname;    _is_java_args = javaargs;    _wc_enabled = cpwildcard;    _ergo_policy = ergo;    // 初始化启动器    InitLauncher(javaw);    // 打印状态    DumpState();    // 跟踪调用启动    if (JLI_IsTraceLauncher()) {        int i;        printf("Command line args:\n");        for (i = 0; i < argc ; i++) {            printf("argv[%d] = %s\n", i, argv[i]);        }        AddOption("-Dsun.java.launcher.diag=true", NULL);    }    /*     * Make sure the specified version of the JRE is running.     *     * There are three things to note about the SelectVersion() routine:     *  1) If the version running isn't correct, this routine doesn't     *     return (either the correct version has been exec'd or an error     *     was issued).     *  2) Argc and Argv in this scope are *not* altered by this routine.     *     It is the responsibility of subsequent code to ignore the     *     arguments handled by this routine.     *  3) As a side-effect, the variable "main_class" is guaranteed to     *     be set (if it should ever be set).  This isn't exactly the     *     poster child for structured programming, but it is a small     *     price to pay for not processing a jar file operand twice.     *     (Note: This side effect has been disabled.  See comment on     *     bugid 5030265 below.)     */    // 解析命令行参数,选择一jre版本    SelectVersion(argc, argv, &main_class);    CreateExecutionEnvironment(&argc, &argv,                               jrepath, sizeof(jrepath),                               jvmpath, sizeof(jvmpath),                               jvmcfg,  sizeof(jvmcfg));    if (!IsJavaArgs()) {        // 设置一些特殊的环境变量        SetJvmEnvironment(argc,argv);    }    ifn.CreateJavaVM = 0;    ifn.GetDefaultJavaVMInitArgs = 0;    if (JLI_IsTraceLauncher()) {        start = CounterGet();    }    // 加载VM, 重中之重    if (!LoadJavaVM(jvmpath, &ifn)) {        return(6);    }    if (JLI_IsTraceLauncher()) {        end   = CounterGet();    }    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",             (long)(jint)Counter2Micros(end-start));    ++argv;    --argc;    // 解析更多参数信息    if (IsJavaArgs()) {        /* Preprocess wrapper arguments */        TranslateApplicationArgs(jargc, jargv, &argc, &argv);        if (!AddApplicationOptions(appclassc, appclassv)) {            return(1);        }    } else {        /* Set default CLASSPATH */        cpath = getenv("CLASSPATH");        if (cpath == NULL) {            cpath = ".";        }        SetClassPath(cpath);    }    /* Parse command line options; if the return value of     * ParseArguments is false, the program should exit.     */    // 解析参数    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))    {        return(ret);    }    /* Override class path if -jar flag was specified */    if (mode == LM_JAR) {        SetClassPath(what);     /* Override class path */    }    /* set the -Dsun.java.command pseudo property */    SetJavaCommandLineProp(what, argc, argv);    /* Set the -Dsun.java.launcher pseudo property */    SetJavaLauncherProp();    /* set the -Dsun.java.launcher.* platform properties */    SetJavaLauncherPlatformProps();    // 进行jvm初始化操作,一般是新开一个线程,然后调用 JavaMain() 实现java代码的权力交接    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);}

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

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


1.2. jre的查找定位与验证

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

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

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

voidCreateExecutionEnvironment(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] = '\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\n", 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] = '\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 = '\0';
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\n"); (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); }}


1.3. 装载jvm链接库

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

// 咱们就只看linux版本的实现好了,原理都一样,各平台实现不同而已(API规范不同)// solaris/bin/java_md_solinux.cjbooleanLoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){    void *libjvm;
JLI_TraceLauncher("JVM path is %s\n", 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.cintJVMInit(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.cintContinueInNewThread(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 */intContinueInNewThread0(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;}

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

// share/bin/java.c// 加载 main 函数类// 通过引入 JavaMain(), 接入java方法// #define JNICALL __stdcallint JNICALLJavaMain(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\n",               (long)(jint)Counter2Micros(end-start));    }    /* At this stage, argc/argv have the application's arguments */    if (JLI_IsTraceLauncher()){        int i;        printf("%s is '%s'\n", launchModeNames[mode], what);        printf("App's argc is %d\n", argc);        for (i=0; i < argc; i++) {            printf("    argv[%2d] = '%s'\n", i, argv[i]);        }    }    ret = 1;    /*     * Get the application's main class.     *     * See bugid 5030265.  The Main-Class name has already been parsed     * from the manifest, but not parsed properly for UTF-8 support.     * Hence the code here ignores the value previously extracted and     * uses the pre-existing code to reextract the value.  This is     * possibly an end of release cycle expedient.  However, it has     * also been discovered that passing some character sets through     * the environment has "strange" behavior on some variants of     * Windows.  Hence, maybe the manifest parsing code local to the     * launcher should never be enhanced.     *     * Hence, future work should either:     *     1)   Correct the local parsing code and verify that the     *          Main-Class attribute gets properly passed through     *          all environments,     *     2)   Remove the vestages of maintaining main_class through     *          the environment (and remove these comments).     *     * This method also correctly handles launching existing JavaFX     * applications that may or may not have a Main-Class manifest entry.     */    // 重点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 jclassLoadMainClass(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\n",               (long)(jint)Counter2Micros(end-start));        printf("----%s----\n", JLDEBUG_ENV_ENTRY);    }    return (jclass)result;}    jclassGetLauncherHelperClass(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 jbooleanInitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn){    JavaVMInitArgs args;    jint r;    memset(&args, 0, sizeof(args));    args.version  = JNI_VERSION_1_2;    args.nOptions = numOptions;    args.options  = options;    args.ignoreUnrecognized = JNI_FALSE;    if (JLI_IsTraceLauncher()) {        int i = 0;        printf("JavaVM args:\n    ");        printf("version 0x%08lx, ", (long)args.version);        printf("ignoreUnrecognized is %s, ",               args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");        printf("nOptions is %ld\n", (long)args.nOptions);        for (i = 0; i < numOptions; i++)            printf("    option[%2d] = '%s'\n",                   i, args.options[i].optionString);    }    // 转交给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 #include
/* jni_md.h contains the machine-dependent typedefs for jbyte, jint and jlong */
#include "jni_md.h"
#ifdef __cplusplusextern "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.cppjint 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.cppJavaThread::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 threadvoid 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(_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 structurestruct 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 functionsstruct 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线程的创建时机进行初始化。而后续的使用中,几乎都会仰仗它来运行,可见其重要性。至于具体的实现,且听后续分解。




往期精彩推荐



腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)

面试:史上最全多线程面试题 !

最新阿里内推Java后端面试题

JVM难学?那是因为你没认真看完这篇文章


END


关注作者微信公众号 —《JAVA烂猪皮》


了解更多java后端架构知识以及最新面试宝典


你点的每个好看,我都认真当成了


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力


作者:等你归去来

出处:https://www.cnblogs.com/yougewe/p/14406217.html

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报