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.*/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);elseJLI_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 beingasked for? Current model isfine unless another modelis asked for */#ifdef SETENV_REQUIREDjboolean 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 thejvmpath 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_REQUIREDmustsetenv = RequiresSetenv(wanted, jvmpath);JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");if (mustsetenv == JNI_FALSE) {JLI_MemFree(newargv);return;}#elseJLI_MemFree(newargv);return;#endif /* SETENV_REQUIRED */} else { /* do the same speculatively or exit */#ifdef DUAL_MODEif (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_REQUIREDmustsetenv = RequiresSetenv(wanted, jvmpath);#endif /* SETENV_REQUIRED */}#else /* ! DUALMODE */JLI_ReportErrorMessage(JRE_ERROR2, wanted);exit(1);#endif /* DUAL_MODE */}#ifdef SETENV_REQUIREDif (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_MODEjrepath, 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 setproperly */}/** 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_REQUIREDif (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_MODEif (running != wanted) {JLI_ReportErrorMessage(JRE_ERROR5, wanted, running);#ifdef __solaris__#ifdef __sparcJLI_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 manpage */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;}}}#endifJLI_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 launchedjmethodID mainID;jobjectArray mainArgs;int ret = 0;jlong start, end;// collectorRegisterThread();/* Initialize the virtual machine */start = CounterGet();// 重点1:初始化jvm,失败则退出// 此处会将重要变量 *env 进程初始化,从而使后续可用if (!InitializeJVM(&vm, &env, &ifn)) {JLI_ReportErrorMessage(JVM_ERROR1);exit(1);}// jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvmif (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/LauncherHelperjclass 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, jintand jlong */#include "jni_md.h"#ifdef __cplusplusextern "C" {#endif/** JNI Types*/#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_Htypedef 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;#endiftypedef 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 USDT2HS_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;// 核心: 创建vmresult = 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 GCRuntimeService::record_application_start();// Notify JVMTIif (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 bootclasspathif (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 possiblesafe_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 versionif (!is_supported_jni_version(args->version)) return JNI_EVERSION;// Initialize the output stream moduleostream_init();// Process java launcher properties.Arguments::process_sun_java_launcher_properties(args);// Initialize the os module before using TLSos::init();// Initialize system properties.Arguments::init_system_properties();// So that JDK version can be used as a discrimintor when parsing argumentsJDK_Version_init();// Update/Initialize System properties after JDK version number is knownArguments::init_version_specific_system_properties();// Parse argumentsjint 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 USDT2HS_DTRACE_PROBE(hotspot, vm__init__begin);#else /* USDT2 */HOTSPOT_VM_INIT_BEGIN();#endif /* USDT2 */// Record VM creation timing statisticsTraceVmCreationTime 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 argsjint 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 TLSThreadLocalStorage::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 loggingostream_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 agentsif (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 heapvm_init_globals();// Attach the main thread to this os threadJavaThread* 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 againreturn 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 subsystemObjectMonitor::Initialize() ;// Second phase of bootstrapping, VM is about entering multi-thread modeMemTracker::bootstrap_multi_thread();// Initialize global modulesjint status = init_globals();if (status != JNI_OK) {delete main_thread;*canTryAgain = false; // don't let caller call JNI_CreateJavaVM againreturn status;}// Should be done after the heap is fully createdmain_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 NMTMemTracker::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 executionJvmtiExport::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 initializedinitialize_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 initializedJDK_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 earlierinitialize_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_enabledif (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 USDT2HS_DTRACE_PROBE(hotspot, vm__init__end);#else /* USDT2 */HOTSPOT_VM_INIT_END();#endif /* USDT2 */// record VM initialization completion time#if INCLUDE_MANAGEMENTManagement::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 YSRif (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 executionJvmtiExport::enter_live_phase();// Signal Dispatcher needs to be started before VMInit event is postedos::signal_init();// Start Attach Listener if +StartAttachListener or it can't be started lazilyif (!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();#endifif (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_MANAGEMENTManagement::initialize(THREAD);#endif // INCLUDE_MANAGEMENTif (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 startos::init_3();create_vm_timer.end();#ifdef ASSERT_vm_complete = true;#endifreturn 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 threadJNIEnv* jni_environment() { return &_jni_environment; }
所以,我们只需找出 _jni_environment 是如何赋值初始化,即可知道如何获取这个关键变量的逻辑了。结果是,在创建JavaThread, 在进行初始化时,便会设置该值。
// share/vm/runtime/thread.cppJavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread(), _satb_mark_queue(&_satb_mark_queue_set),_dirty_card_queue(&_dirty_card_queue_set){if (TraceThreadEvents) {tty->print_cr("creating thread %p", this);}// 初始化线程变量信息, 如 JNIEnvinitialize();_jni_attach_state = _not_attaching_via_jni;set_entry_point(entry_point);// Create the native thread itself.// %note runtime_23os::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_environmentset_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);set_recorder(NULL);_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) ;_jmp_ring_index = 0;for (int ji = 0 ; ji < jump_ring_buffer_size ; ji++ ) {record_jump(NULL, NULL, NULL, 0);}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 threadThreadSafepointState::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 (CheckJNICalls) return jni_functions_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 featuresjni_GetObjectRefType};
以上就是 JNIEnv* env 变量的设值过程了,它借助于java线程的创建时机进行初始化。而后续的使用中,几乎都会仰仗它来运行,可见其重要性。至于具体的实现,且听后续分解。

腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)
面试:史上最全多线程面试题 !
最新阿里内推Java后端面试题
JVM难学?那是因为你没认真看完这篇文章

关注作者微信公众号 —《JAVA烂猪皮》
了解更多java后端架构知识以及最新面试宝典


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力
作者:等你归去来
出处:https://www.cnblogs.com/yougewe/p/14406217.html
