上兩篇中梳理了整個java啟動過程中,jvm大致是如何執行的。即釐清了我們認為的jvm的啟動過程。但那裡面僅為一些大致的東西,比如引數解析,驗證,dll載入等等。把最核心的loadJavaVM()交給了一個dll或者so庫。也就是真正的jvm我們並沒有接觸到,我們僅看了一個包裝者或者是上層應用的實現。即我們僅是在jdk的角度看了下虛擬機器,這需要更深入一點。
1. 回顧jvm載入框架
雖然jvm的載入核心並不在jdk中,但它確實沒有自己的簡易入口。也就是說jvm想要啟動,還得依靠jdk. 所以,讓我們回顧下jdk是如何帶動jvm的?
1.1. java啟動框架
自然是在 JLI_Launch 的入口檢視了。
// share/bin/java.c /* * Entry point. */ int JLI_Launch(int argc, char ** argv, /* main argc, argc */ int jargc, const char** jargv, /* java args */ int appclassc, const char** appclassv, /* app classpath */ const char* fullversion, /* full version defined */ const char* dotversion, /* dot version defined */ const char* pname, /* program name */ const char* lname, /* launcher name */ jboolean javaargs, /* JAVA_ARGS */ jboolean cpwildcard, /* classpath wildcard*/ jboolean javaw, /* windows-only javaw */ jint ergo /* ergonomics class policy */ ) { int mode = LM_UNKNOWN; char *what = NULL; char *cpath = 0; char *main_class = NULL; int ret; InvocationFunctions ifn; jlong start, end; char jvmpath[MAXPATHLEN]; char jrepath[MAXPATHLEN]; char jvmcfg[MAXPATHLEN]; _fVersion = fullversion; _dVersion = dotversion; _launcher_name = lname; _program_name = pname; _is_java_args = javaargs; _wc_enabled = cpwildcard; _ergo_policy = ergo; // 初始化啟動器 InitLauncher(javaw); // 列印狀態 DumpState(); // 跟蹤呼叫啟動 if (JLI_IsTraceLauncher()) { int i; printf("Command line args:\n"); for (i = 0; i < argc ; i++) { printf("argv[%d] = %s\n", i, argv[i]); } AddOption("-Dsun.java.launcher.diag=true", NULL); } /* * Make sure the specified version of the JRE is running. * * There are three things to note about the SelectVersion() routine: * 1) If the version running isn't correct, this routine doesn't * return (either the correct version has been exec'd or an error * was issued). * 2) Argc and Argv in this scope are *not* altered by this routine. * It is the responsibility of subsequent code to ignore the * arguments handled by this routine. * 3) As a side-effect, the variable "main_class" is guaranteed to * be set (if it should ever be set). This isn't exactly the * poster child for structured programming, but it is a small * price to pay for not processing a jar file operand twice. * (Note: This side effect has been disabled. See comment on * bugid 5030265 below.) */ // 解析命令列引數,選擇一jre版本 SelectVersion(argc, argv, &main_class); CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), jvmcfg, sizeof(jvmcfg)); if (!IsJavaArgs()) { // 設定一些特殊的環境變數 SetJvmEnvironment(argc,argv); } ifn.CreateJavaVM = 0; ifn.GetDefaultJavaVMInitArgs = 0; if (JLI_IsTraceLauncher()) { start = CounterGet(); } // 載入VM, 重中之重 if (!LoadJavaVM(jvmpath, &ifn)) { return(6); } if (JLI_IsTraceLauncher()) { end = CounterGet(); } JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n", (long)(jint)Counter2Micros(end-start)); ++argv; --argc; // 解析更多引數資訊 if (IsJavaArgs()) { /* Preprocess wrapper arguments */ TranslateApplicationArgs(jargc, jargv, &argc, &argv); if (!AddApplicationOptions(appclassc, appclassv)) { return(1); } } else { /* Set default CLASSPATH */ cpath = getenv("CLASSPATH"); if (cpath == NULL) { cpath = "."; } SetClassPath(cpath); } /* Parse command line options; if the return value of * ParseArguments is false, the program should exit. */ // 解析引數 if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) { return(ret); } /* Override class path if -jar flag was specified */ if (mode == LM_JAR) { SetClassPath(what); /* Override class path */ } /* set the -Dsun.java.command pseudo property */ SetJavaCommandLineProp(what, argc, argv); /* Set the -Dsun.java.launcher pseudo property */ SetJavaLauncherProp(); /* set the -Dsun.java.launcher.* platform properties */ SetJavaLauncherPlatformProps(); // 進行jvm初始化操作,一般是新開一個執行緒,然後呼叫 JavaMain() 實現java程式碼的權力交接 return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret); }
以上就是java啟動jvm的核心框架。和真正的jvm相關的兩個:1. SelectVersion() 會查詢系統中存在的jvm即jre版本,是否可以被當前使用,以及main_class的驗證;2. 在初始化時會呼叫jvm的 CreateJavaVM()方法,進行jvm真正的建立交接,這是通過函式指標實現的;
具體兩個相關操作需要分解下,因為這些過程還是略微複雜的。
1.2. jre的查詢定位與驗證
要執行jvm,首先就是要確定系統中是否安裝了相應的jre環境,並確定版本是否正確。
// java.c /* * The SelectVersion() routine ensures that an appropriate version of * the JRE is running. The specification for the appropriate version * is obtained from either the manifest of a jar file (preferred) or * from command line options. * The routine also parses splash screen command line options and * passes on their values in private environment variables. */ static void SelectVersion(int argc, char **argv, char **main_class) { char *arg; char **new_argv; char **new_argp; char *operand; char *version = NULL; char *jre = NULL; int jarflag = 0; int headlessflag = 0; int restrict_search = -1; /* -1 implies not known */ manifest_info info; char env_entry[MAXNAMELEN + 24] = ENV_ENTRY "="; char *splash_file_name = NULL; char *splash_jar_name = NULL; char *env_in; int res; /* * If the version has already been selected, set *main_class * with the value passed through the environment (if any) and * simply return. */ // _JAVA_VERSION_SET= if ((env_in = getenv(ENV_ENTRY)) != NULL) { if (*env_in != '\0') *main_class = JLI_StringDup(env_in); return; } /* * Scan through the arguments for options relevant to multiple JRE * support. For reference, the command line syntax is defined as: * * SYNOPSIS * java [options] class [argument...] * * java [options] -jar file.jar [argument...] * * As the scan is performed, make a copy of the argument list with * the version specification options (new to 1.5) removed, so that * a version less than 1.5 can be exec'd. * * Note that due to the syntax of the native Windows interface * CreateProcess(), processing similar to the following exists in * the Windows platform specific routine ExecJRE (in java_md.c). * Changes here should be reproduced there. */ new_argv = JLI_MemAlloc((argc + 1) * sizeof(char*)); new_argv[0] = argv[0]; new_argp = &new_argv[1]; argc--; argv++; while ((arg = *argv) != 0 && *arg == '-') { if (JLI_StrCCmp(arg, "-version:") == 0) { version = arg + 9; } else if (JLI_StrCmp(arg, "-jre-restrict-search") == 0) { restrict_search = 1; } else if (JLI_StrCmp(arg, "-no-jre-restrict-search") == 0) { restrict_search = 0; } else { if (JLI_StrCmp(arg, "-jar") == 0) jarflag = 1; /* deal with "unfortunate" classpath syntax */ if ((JLI_StrCmp(arg, "-classpath") == 0 || JLI_StrCmp(arg, "-cp") == 0) && (argc >= 2)) { *new_argp++ = arg; argc--; argv++; arg = *argv; } /* * Checking for headless toolkit option in the some way as AWT does: * "true" means true and any other value means false */ if (JLI_StrCmp(arg, "-Djava.awt.headless=true") == 0) { headlessflag = 1; } else if (JLI_StrCCmp(arg, "-Djava.awt.headless=") == 0) { headlessflag = 0; } else if (JLI_StrCCmp(arg, "-splash:") == 0) { splash_file_name = arg+8; } *new_argp++ = arg; } argc--; argv++; } if (argc <= 0) { /* No operand? Possibly legit with -[full]version */ operand = NULL; } else { argc--; *new_argp++ = operand = *argv++; } while (argc-- > 0) /* Copy over [argument...] */ *new_argp++ = *argv++; *new_argp = NULL; /* * If there is a jar file, read the manifest. If the jarfile can't be * read, the manifest can't be read from the jar file, or the manifest * is corrupt, issue the appropriate error messages and exit. * * Even if there isn't a jar file, construct a manifest_info structure * containing the command line information. It's a convenient way to carry * this data around. */ if (jarflag && operand) { if ((res = JLI_ParseManifest(operand, &info)) != 0) { if (res == -1) JLI_ReportErrorMessage(JAR_ERROR2, operand); else JLI_ReportErrorMessage(JAR_ERROR3, operand); exit(1); } /* * Command line splash screen option should have precedence * over the manifest, so the manifest data is used only if * splash_file_name has not been initialized above during command * line parsing */ if (!headlessflag && !splash_file_name && info.splashscreen_image_file_name) { splash_file_name = info.splashscreen_image_file_name; splash_jar_name = 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. */ void ExecJRE(char *jre, char **argv) { char wanted[PATH_MAX]; const char* progname = GetProgramName(); const char* execname = NULL; /* * Resolve the real path to the directory containing the selected JRE. */ if (realpath(jre, wanted) == NULL) { JLI_ReportErrorMessage(JRE_ERROR9, jre); exit(1); } /* * Resolve the real path to the currently running launcher. */ SetExecname(argv); execname = GetExecName(); if (execname == NULL) { JLI_ReportErrorMessage(JRE_ERROR10); exit(1); } /* * If the path to the selected JRE directory is a match to the initial * portion of the path to the currently executing JRE, we have a winner! * If so, just return. */ if (JLI_StrNCmp(wanted, execname, JLI_StrLen(wanted)) == 0) return; /* I am the droid you were looking for */ /* * This should never happen (because of the selection code in SelectJRE), * but check for "impossibly" long path names just because buffer overruns * can be so deadly. */ if (JLI_StrLen(wanted) + JLI_StrLen(progname) + 6 > PATH_MAX) { JLI_ReportErrorMessage(JRE_ERROR11); exit(1); } /* * Construct the path and exec it. */ (void)JLI_StrCat(JLI_StrCat(wanted, "/bin/"), progname); argv[0] = JLI_StringDup(progname); if (JLI_IsTraceLauncher()) { int i; printf("ReExec Command: %s (%s)\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); }
接下來有個環境準備的過程,有點複雜。主要就是根據不同的平臺要求,設定一些環境變數,想看更多的同學自行展開。
void CreateExecutionEnvironment(int *pargc, char ***pargv, char jrepath[], jint so_jrepath, char jvmpath[], jint so_jvmpath, char jvmcfg[], jint so_jvmcfg) { /* * First, determine if we are running the desired data model. If we * are running the desired data model, all the error messages * associated with calling GetJREPath, ReadKnownVMs, etc. should be * output. However, if we are not running the desired data model, * some of the errors should be suppressed since it is more * informative to issue an error message based on whether or not the * os/processor combination has dual mode capabilities. */ jboolean jvmpathExists; /* Compute/set the name of the executable */ SetExecname(*pargv); /* Check data model flags, and exec process, if needed */ { char *arch = (char *)GetArch(); /* like sparc or sparcv9 */ char * jvmtype = NULL; int argc = *pargc; char **argv = *pargv; int running = CURRENT_DATA_MODEL; int wanted = running; /* What data mode is being asked for? Current model is fine unless another model is asked for */ #ifdef SETENV_REQUIRED jboolean mustsetenv = JNI_FALSE; char *runpath = NULL; /* existing effective LD_LIBRARY_PATH setting */ char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */ char* newpath = NULL; /* path on new LD_LIBRARY_PATH */ char* lastslash = NULL; char** newenvp = NULL; /* current environment */ #ifdef __solaris__ char* dmpath = NULL; /* data model specific LD_LIBRARY_PATH, Solaris only */ #endif /* __solaris__ */ #endif /* SETENV_REQUIRED */ char** newargv = NULL; int newargc = 0; /* * Starting in 1.5, all unix platforms accept the -d32 and -d64 * options. On platforms where only one data-model is supported * (e.g. ia-64 Linux), using the flag for the other data model is * an error and will terminate the program. */ { /* open new scope to declare local variables */ int i; newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*)); newargv[newargc++] = argv[0]; /* scan for data model arguments and remove from argument list; last occurrence determines desired data model */ for (i=1; i < argc; i++) { if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) { wanted = 64; continue; } if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) { wanted = 32; continue; } newargv[newargc++] = argv[i]; if (IsJavaArgs()) { if (argv[i][0] != '-') continue; } else { if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) { i++; if (i >= argc) break; newargv[newargc++] = argv[i]; continue; } if (argv[i][0] != '-') { i++; break; } } } /* copy rest of args [i .. argc) */ while (i < argc) { newargv[newargc++] = argv[i++]; } newargv[newargc] = NULL; /* * newargv has all proper arguments here */ argc = newargc; argv = newargv; } /* If the data model is not changing, it is an error if the jvmpath does not exist */ if (wanted == running) { /* Find out where the JRE is that we will be using. */ if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) { JLI_ReportErrorMessage(JRE_ERROR1); exit(2); } JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg", jrepath, FILESEP, FILESEP, arch, FILESEP); /* Find the specified JVM type */ if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) { JLI_ReportErrorMessage(CFG_ERROR7); exit(1); } jvmpath[0] = '\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.c jboolean LoadJavaVM(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.c int JVMInit(InvocationFunctions* ifn, jlong threadStackSize, int argc, char **argv, int mode, char *what, int ret) { ShowSplashScreen(); return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret); } // java.c int ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize, int argc, char **argv, int mode, char *what, int ret) { /* * If user doesn't specify stack size, check if VM has a preference. * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will * return its default stack size through the init args structure. */ if (threadStackSize == 0) { struct JDK1_1InitArgs args1_1; memset((void*)&args1_1, 0, sizeof(args1_1)); args1_1.version = JNI_VERSION_1_1; ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */ if (args1_1.javaStackSize > 0) { threadStackSize = args1_1.javaStackSize; } } { /* Create a new thread to create JVM and invoke main method */ JavaMainArgs args; int rslt; args.argc = argc; args.argv = argv; args.mode = mode; args.what = what; args.ifn = *ifn; // 傳入 JavaMain, 在新執行緒中呼叫 rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args); /* If the caller has deemed there is an error we * simply return that, otherwise we return the value of * the callee */ return (ret != 0) ? ret : rslt; } } // solaris/bin/java_md_solinux.c /* * Block current thread and continue execution in a new thread */ int ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) { int rslt; #ifdef __linux__ pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); if (stack_size > 0) { pthread_attr_setstacksize(&attr, stack_size); } // 常見的 pthread_xx 方式 建立執行緒 if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) { void * tmp; pthread_join(tid, &tmp); rslt = (int)tmp; } else { /* * Continue execution in current thread if for some reason (e.g. out of * memory/LWP) a new thread can't be created. This will likely fail * later in continuation as JNI_CreateJavaVM needs to create quite a * few new threads, anyway, just give it a try.. */ rslt = continuation(args); } pthread_attr_destroy(&attr); #else /* ! __linux__ */ thread_t tid; long flags = 0; if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) { void * tmp; thr_join(tid, NULL, &tmp); rslt = (int)tmp; } else { /* See above. Continue in current thread if thr_create() failed */ rslt = continuation(args); } #endif /* __linux__ */ return rslt; }
JavaMain() 是執行java程式碼或者建立jvm的核心入口。
// share/bin/java.c // 載入 main 函式類 // 通過引入 JavaMain(), 接入java方法 // #define JNICALL __stdcall int JNICALL JavaMain(void * _args) { JavaMainArgs *args = (JavaMainArgs *)_args; int argc = args->argc; char **argv = args->argv; int mode = args->mode; char *what = args->what; // 一些jvm的呼叫例項,在之前的步驟中,通過載入相應動態連結方法,儲存起來的 /** * ifn->CreateJavaVM = * (void *)GetProcAddress(handle, "JNI_CreateJavaVM"); * ifn->GetDefaultJavaVMInitArgs = * (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs"); */ InvocationFunctions ifn = args->ifn; JavaVM *vm = 0; JNIEnv *env = 0; jclass mainClass = NULL; jclass appClass = NULL; // actual application class being launched jmethodID mainID; jobjectArray mainArgs; int ret = 0; jlong start, end; // collector RegisterThread(); /* Initialize the virtual machine */ start = CounterGet(); // 重點1:初始化jvm,失敗則退出 // 此處會將重要變數 *env 程式初始化,從而使後續可用 if (!InitializeJVM(&vm, &env, &ifn)) { JLI_ReportErrorMessage(JVM_ERROR1); exit(1); } // jvm檢查完畢,如果只是一些展示類請求,則展示資訊後,退出jvm if (showSettings != NULL) { ShowSettings(env, showSettings); /** * 巨集是神奇的操作,此處 *env 直接引用 #define CHECK_EXCEPTION_LEAVE(CEL_return_value) \ do { \ if ((*env)->ExceptionOccurred(env)) { \ JLI_ReportExceptionDescription(env); \ ret = (CEL_return_value); \ LEAVE(); \ } \ } while (JNI_FALSE) */ CHECK_EXCEPTION_LEAVE(1); } // 呼叫 LEAVE() 方法的目的在於主動銷燬jvm執行緒 // 且退出當前方法呼叫,即 LEAVE() 後方法不再被執行 /* * Always detach the main thread so that it appears to have ended when * the application's main method exits. This will invoke the * uncaught exception handler machinery if main threw an * exception. An uncaught exception handler cannot change the * launcher's return code except by calling System.exit. * * Wait for all non-daemon threads to end, then destroy the VM. * This will actually create a trivial new Java waiter thread * named "DestroyJavaVM", but this will be seen as a different * thread from the one that executed main, even though they are * the same C thread. This allows mainThread.join() and * mainThread.isAlive() to work as expected. */ /** * * #define LEAVE() \ do { \ if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \ JLI_ReportErrorMessage(JVM_ERROR2); \ ret = 1; \ } \ if (JNI_TRUE) { \ (*vm)->DestroyJavaVM(vm); \ return ret; \ } \ } while (JNI_FALSE) */ if (printVersion || showVersion) { PrintJavaVersion(env, showVersion); CHECK_EXCEPTION_LEAVE(0); if (printVersion) { LEAVE(); } } /* If the user specified neither a class name nor a JAR file */ if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) { PrintUsage(env, printXUsage); CHECK_EXCEPTION_LEAVE(1); LEAVE(); } // 釋放記憶體 FreeKnownVMs(); /* after last possible PrintUsage() */ if (JLI_IsTraceLauncher()) { end = CounterGet(); JLI_TraceLauncher("%ld micro seconds to InitializeJVM\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 jclass LoadMainClass(JNIEnv *env, int mode, char *name) { jmethodID mid; jstring str; jobject result; jlong start, end; // sun/launcher/LauncherHelper jclass cls = GetLauncherHelperClass(env); NULL_CHECK0(cls); if (JLI_IsTraceLauncher()) { start = CounterGet(); } // checkAndLoadMain(String) 方法作為中間main()呼叫 NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls, "checkAndLoadMain", "(ZILjava/lang/String;)Ljava/lang/Class;")); str = NewPlatformString(env, name); CHECK_JNI_RETURN_0( result = (*env)->CallStaticObjectMethod( env, cls, mid, USE_STDERR, mode, str)); if (JLI_IsTraceLauncher()) { end = CounterGet(); printf("%ld micro seconds to load main class\n", (long)(jint)Counter2Micros(end-start)); printf("----%s----\n", JLDEBUG_ENV_ENTRY); } return (jclass)result; } jclass GetLauncherHelperClass(JNIEnv *env) { if (helperClass == NULL) { NULL_CHECK0(helperClass = FindBootStrapClass(env, "sun/launcher/LauncherHelper")); } return helperClass; }
JavaMain 框架大概就是初始化建立jvm, 查詢mainClass類, 找到函式指定, 構建args引數, 執行main(), 以及其他的一些相容性處理。當JavaMain 執行完成時,則意味著整個jvm就完成了。所以,這也成為了我們要研究的重中之重。
2. 真正的jvm建立
jdk是我們看到的jvm前端,而背後的jre或者jvm才是大佬。它是在 JavaMain() 中觸發呼叫的。也就是上節中看到的框架結構。初始化JVM的過程,實際就是呼叫jvm的函式指標 JNI_CreateJavaVM 地過程。
// 初始化jvm, 主要是呼叫 CreateJavaVM() 方法,進行建立jvm操作 /* * Initializes the Java Virtual Machine. Also frees options array when * finished. */ static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn) { JavaVMInitArgs args; jint r; memset(&args, 0, sizeof(args)); args.version = JNI_VERSION_1_2; args.nOptions = numOptions; args.options = options; args.ignoreUnrecognized = JNI_FALSE; if (JLI_IsTraceLauncher()) { int i = 0; printf("JavaVM args:\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 <stdio.h> #include <stdarg.h> /* jni_md.h contains the machine-dependent typedefs for jbyte, jint and jlong */ #include "jni_md.h" #ifdef __cplusplus extern "C" { #endif /* * JNI Types */ #ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; typedef jint jsize; #ifdef __cplusplus // c++版本的物件定義 class _jobject {}; class _jclass : public _jobject {}; class _jthrowable : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jbooleanArray : public _jarray {}; class _jbyteArray : public _jarray {}; class _jcharArray : public _jarray {}; class _jshortArray : public _jarray {}; class _jintArray : public _jarray {}; class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jobjectArray : public _jarray {}; typedef _jobject *jobject; typedef _jclass *jclass; typedef _jthrowable *jthrowable; typedef _jstring *jstring; typedef _jarray *jarray; typedef _jbooleanArray *jbooleanArray; typedef _jbyteArray *jbyteArray; typedef _jcharArray *jcharArray; typedef _jshortArray *jshortArray; typedef _jintArray *jintArray; typedef _jlongArray *jlongArray; typedef _jfloatArray *jfloatArray; typedef _jdoubleArray *jdoubleArray; typedef _jobjectArray *jobjectArray; #else // c版本的物件定義 struct _jobject; typedef struct _jobject *jobject; typedef jobject jclass; typedef jobject jthrowable; typedef jobject jstring; typedef jobject jarray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jarray jobjectArray; #endif typedef jobject jweak; // java各型別的定義簡寫 typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue; struct _jfieldID; typedef struct _jfieldID *jfieldID; struct _jmethodID; typedef struct _jmethodID *jmethodID; /* Return values from jobjectRefType */ typedef enum _jobjectType { JNIInvalidRefType = 0, JNILocalRefType = 1, JNIGlobalRefType = 2, JNIWeakGlobalRefType = 3 } jobjectRefType; #endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */ /* * jboolean constants */ #define JNI_FALSE 0 #define JNI_TRUE 1 /* * possible return values for JNI functions. */ #define JNI_OK 0 /* success */ #define JNI_ERR (-1) /* unknown error */ #define JNI_EDETACHED (-2) /* thread detached from the VM */ #define JNI_EVERSION (-3) /* JNI version error */ #define JNI_ENOMEM (-4) /* not enough memory */ #define JNI_EEXIST (-5) /* VM already created */ #define JNI_EINVAL (-6) /* invalid arguments */ /* * used in ReleaseScalarArrayElements */ #define JNI_COMMIT 1 #define JNI_ABORT 2 /* * used in RegisterNatives to describe native method name, signature, * and function pointer. */ typedef struct { char *name; char *signature; void *fnPtr; } JNINativeMethod; ...
大概就是相容各平臺,可能使用C實現,也可能使用C++實現。完整版本請參考官網: http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/6ea5a8067d1f/src/share/vm/prims/jni.h
其中,有兩個比較重的結構體的定義:JNINativeInterface_ 是jni呼叫的大部分介面定義,基本上可以通過它呼叫任意方法。JNIEnv_ 是每個方法呼叫時的上下文管理器,它負責呼叫 JNINativeInterface_ 的方法,相當於是C++版本的JNINativeInterface_。
3. jvm核心建立框架
在jni.h中,還有很多C++或者C的判斷,但對於CreateJavaVM這件事,就變成了一個純粹C++的實現了。
// hotspot/src/share/vm/prims/jni.cpp _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) { #ifndef USDT2 HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args); #else /* USDT2 */ HOTSPOT_JNI_CREATEJAVAVM_ENTRY( (void **) vm, penv, args); #endif /* USDT2 */ jint result = JNI_ERR; DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result); // We're about to use Atomic::xchg for synchronization. Some Zero // platforms use the GCC builtin __sync_lock_test_and_set for this, // but __sync_lock_test_and_set is not guaranteed to do what we want // on all architectures. So we check it works before relying on it. #if defined(ZERO) && defined(ASSERT) { // java 魔術頭 jint a = 0xcafebabe; jint b = Atomic::xchg(0xdeadbeef, &a); void *c = &a; void *d = Atomic::xchg_ptr(&b, &c); assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works"); assert(c == &b && d == &a, "Atomic::xchg_ptr() works"); } #endif // ZERO && ASSERT // At the moment it's only possible to have one Java VM, // since some of the runtime state is in global variables. // We cannot use our mutex locks here, since they only work on // Threads. We do an atomic compare and exchange to ensure only // one thread can call this method at a time // We use Atomic::xchg rather than Atomic::add/dec since on some platforms // the add/dec implementations are dependent on whether we are running // on a multiprocessor, and at this stage of initialization the os::is_MP // function used to determine this will always return false. Atomic::xchg // does not have this problem. if (Atomic::xchg(1, &vm_created) == 1) { return JNI_EEXIST; // already created, or create attempt in progress } if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) { return JNI_ERR; // someone tried and failed and retry not allowed. } assert(vm_created == 1, "vm_created is true during the creation"); /** * Certain errors during initialization are recoverable and do not * prevent this method from being called again at a later time * (perhaps with different arguments). However, at a certain * point during initialization if an error occurs we cannot allow * this function to be called again (or it will crash). In those * situations, the 'canTryAgain' flag is set to false, which atomically * sets safe_to_recreate_vm to 1, such that any new call to * JNI_CreateJavaVM will immediately fail using the above logic. */ bool can_try_again = true; // 核心: 建立vm result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again); if (result == JNI_OK) { JavaThread *thread = JavaThread::current(); /* thread is thread_in_vm here */ *vm = (JavaVM *)(&main_vm); // 將jvm資訊儲存到 penv 中,以備外部使用 *(JNIEnv**)penv = thread->jni_environment(); // Tracks the time application was running before GC RuntimeService::record_application_start(); // Notify JVMTI if (JvmtiExport::should_post_thread_life()) { JvmtiExport::post_thread_start(thread); } EventThreadStart event; if (event.should_commit()) { event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj())); event.commit(); } #ifndef PRODUCT #ifndef TARGET_OS_FAMILY_windows #define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f() #endif // Check if we should compile all classes on bootclasspath if (CompileTheWorld) ClassLoader::compile_the_world(); if (ReplayCompiles) ciReplay::replay(thread); // Some platforms (like Win*) need a wrapper around these test // functions in order to properly handle error conditions. CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler); CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests); #endif // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving. ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native); } else { // 建立VM失敗, 還原標識位資訊 if (can_try_again) { // reset safe_to_recreate_vm to 1 so that retrial would be possible safe_to_recreate_vm = 1; } // Creation failed. We must reset vm_created *vm = 0; *(JNIEnv**)penv = 0; // reset vm_created last to avoid race condition. Use OrderAccess to // control both compiler and architectural-based reordering. OrderAccess::release_store(&vm_created, 0); } return result; }
看C++程式碼果然有點費勁,不過有著註釋的加持,還算可以理解。大致就是測試環境,然後上CAS鎖,保證vm載入時的執行緒安全性,進行vm建立,然後將vm的環境資訊賦值給 外部 penv, 測試下vm有效性, 返回建立狀態。
核心仍然是被包裹著的:Threads::create_vm()
// share/vm/runtime/thread.cpp jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { extern void JDK_Version_init(); // Check version if (!is_supported_jni_version(args->version)) return JNI_EVERSION; // Initialize the output stream module ostream_init(); // Process java launcher properties. Arguments::process_sun_java_launcher_properties(args); // Initialize the os module before using TLS os::init(); // Initialize system properties. Arguments::init_system_properties(); // So that JDK version can be used as a discrimintor when parsing arguments JDK_Version_init(); // Update/Initialize System properties after JDK version number is known Arguments::init_version_specific_system_properties(); // Parse arguments jint parse_result = Arguments::parse(args); if (parse_result != JNI_OK) return parse_result; os::init_before_ergo(); jint ergo_result = Arguments::apply_ergo(); if (ergo_result != JNI_OK) return ergo_result; if (PauseAtStartup) { os::pause(); } #ifndef USDT2 HS_DTRACE_PROBE(hotspot, vm__init__begin); #else /* USDT2 */ HOTSPOT_VM_INIT_BEGIN(); #endif /* USDT2 */ // Record VM creation timing statistics TraceVmCreationTime create_vm_timer; create_vm_timer.start(); // Timing (must come after argument parsing) TraceTime timer("Create VM", TraceStartupTime); // Initialize the os module after parsing the args jint os_init_2_result = os::init_2(); if (os_init_2_result != JNI_OK) return os_init_2_result; jint adjust_after_os_result = Arguments::adjust_after_os(); if (adjust_after_os_result != JNI_OK) return adjust_after_os_result; // intialize TLS ThreadLocalStorage::init(); // Bootstrap native memory tracking, so it can start recording memory // activities before worker thread is started. This is the first phase // of bootstrapping, VM is currently running in single-thread mode. MemTracker::bootstrap_single_thread(); // Initialize output stream logging ostream_init_log(); // Convert -Xrun to -agentlib: if there is no JVM_OnLoad // Must be before create_vm_init_agents() if (Arguments::init_libraries_at_startup()) { convert_vm_init_libraries_to_agents(); } // Launch -agentlib/-agentpath and converted -Xrun agents if (Arguments::init_agents_at_startup()) { create_vm_init_agents(); } // Initialize Threads state _thread_list = NULL; _number_of_threads = 0; _number_of_non_daemon_threads = 0; // Initialize global data structures and create system classes in heap vm_init_globals(); // Attach the main thread to this os thread JavaThread* main_thread = new JavaThread(); main_thread->set_thread_state(_thread_in_vm); // must do this before set_active_handles and initialize_thread_local_storage // Note: on solaris initialize_thread_local_storage() will (indirectly) // change the stack size recorded here to one based on the java thread // stacksize. This adjusted size is what is used to figure the placement // of the guard pages. main_thread->record_stack_base_and_size(); main_thread->initialize_thread_local_storage(); main_thread->set_active_handles(JNIHandleBlock::allocate_block()); if (!main_thread->set_as_starting_thread()) { vm_shutdown_during_initialization( "Failed necessary internal allocation. Out of swap space"); delete main_thread; *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again return JNI_ENOMEM; } // Enable guard page *after* os::create_main_thread(), otherwise it would // crash Linux VM, see notes in os_linux.cpp. main_thread->create_stack_guard_pages(); // Initialize Java-Level synchronization subsystem ObjectMonitor::Initialize() ; // Second phase of bootstrapping, VM is about entering multi-thread mode MemTracker::bootstrap_multi_thread(); // Initialize global modules jint status = init_globals(); if (status != JNI_OK) { delete main_thread; *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again return status; } // Should be done after the heap is fully created main_thread->cache_global_variables(); HandleMark hm; { MutexLocker mu(Threads_lock); Threads::add(main_thread); } // Any JVMTI raw monitors entered in onload will transition into // real raw monitor. VM is setup enough here for raw monitor enter. JvmtiExport::transition_pending_onload_raw_monitors(); // Fully start NMT MemTracker::start(); // Create the VMThread { TraceTime timer("Start VMThread", TraceStartupTime); VMThread::create(); Thread* vmthread = VMThread::vm_thread(); if (!os::create_thread(vmthread, os::vm_thread)) vm_exit_during_initialization("Cannot create VM thread. Out of system resources."); // Wait for the VM thread to become ready, and VMThread::run to initialize // Monitors can have spurious returns, must always check another state flag { MutexLocker ml(Notify_lock); os::start_thread(vmthread); while (vmthread->active_handles() == NULL) { Notify_lock->wait(); } } } assert (Universe::is_fully_initialized(), "not initialized"); if (VerifyDuringStartup) { // Make sure we're starting with a clean slate. VM_Verify verify_op; VMThread::execute(&verify_op); } EXCEPTION_MARK; // At this point, the Universe is initialized, but we have not executed // any byte code. Now is a good time (the only time) to dump out the // internal state of the JVM for sharing. if (DumpSharedSpaces) { MetaspaceShared::preload_and_dump(CHECK_0); ShouldNotReachHere(); } // Always call even when there are not JVMTI environments yet, since environments // may be attached late and JVMTI must track phases of VM execution JvmtiExport::enter_start_phase(); // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents. JvmtiExport::post_vm_start(); { TraceTime timer("Initialize java.lang classes", TraceStartupTime); if (EagerXrunInit && Arguments::init_libraries_at_startup()) { create_vm_init_libraries(); } initialize_class(vmSymbols::java_lang_String(), CHECK_0); // Initialize java_lang.System (needed before creating the thread) initialize_class(vmSymbols::java_lang_System(), CHECK_0); initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0); Handle thread_group = create_initial_thread_group(CHECK_0); Universe::set_main_thread_group(thread_group()); initialize_class(vmSymbols::java_lang_Thread(), CHECK_0); oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0); main_thread->set_threadObj(thread_object); // Set thread status to running since main thread has // been started and running. java_lang_Thread::set_thread_status(thread_object, java_lang_Thread::RUNNABLE); // The VM creates & returns objects of this class. Make sure it's initialized. initialize_class(vmSymbols::java_lang_Class(), CHECK_0); // The VM preresolves methods to these classes. Make sure that they get initialized initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0); initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0); call_initializeSystemClass(CHECK_0); // get the Java runtime name after java.lang.System is initialized JDK_Version::set_runtime_name(get_java_runtime_name(THREAD)); JDK_Version::set_runtime_version(get_java_runtime_version(THREAD)); // an instance of OutOfMemory exception has been allocated earlier initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0); initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0); initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0); initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0); initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0); initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0); initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0); initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0); } // See : bugid 4211085. // Background : the static initializer of java.lang.Compiler tries to read // property"java.compiler" and read & write property "java.vm.info". // When a security manager is installed through the command line // option "-Djava.security.manager", the above properties are not // readable and the static initializer for java.lang.Compiler fails // resulting in a NoClassDefFoundError. This can happen in any // user code which calls methods in java.lang.Compiler. // Hack : the hack is to pre-load and initialize this class, so that only // system domains are on the stack when the properties are read. // Currently even the AWT code has calls to methods in java.lang.Compiler. // On the classic VM, java.lang.Compiler is loaded very early to load the JIT. // Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and // read and write"java.vm.info" in the default policy file. See bugid 4211383 // Once that is done, we should remove this hack. initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0); // More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to // the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot // compiler does not get loaded through java.lang.Compiler). "java -version" with the // hotspot vm says "nojit" all the time which is confusing. So, we reset it here. // This should also be taken out as soon as 4211383 gets fixed. reset_vm_info_property(CHECK_0); quicken_jni_functions(); // Must be run after init_ft which initializes ft_enabled if (TRACE_INITIALIZE() != JNI_OK) { vm_exit_during_initialization("Failed to initialize tracing backend"); } // Set flag that basic initialization has completed. Used by exceptions and various // debug stuff, that does not work until all basic classes have been initialized. set_init_completed(); #ifndef USDT2 HS_DTRACE_PROBE(hotspot, vm__init__end); #else /* USDT2 */ HOTSPOT_VM_INIT_END(); #endif /* USDT2 */ // record VM initialization completion time #if INCLUDE_MANAGEMENT Management::record_vm_init_completed(); #endif // INCLUDE_MANAGEMENT // Compute system loader. Note that this has to occur after set_init_completed, since // valid exceptions may be thrown in the process. // Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and // set_init_completed has just been called, causing exceptions not to be shortcut // anymore. We call vm_exit_during_initialization directly instead. SystemDictionary::compute_java_system_loader(THREAD); if (HAS_PENDING_EXCEPTION) { vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION)); } #if INCLUDE_ALL_GCS // Support for ConcurrentMarkSweep. This should be cleaned up // and better encapsulated. The ugly nested if test would go away // once things are properly refactored. XXX YSR if (UseConcMarkSweepGC || UseG1GC) { if (UseConcMarkSweepGC) { ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD); } else { ConcurrentMarkThread::makeSurrogateLockerThread(THREAD); } if (HAS_PENDING_EXCEPTION) { vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION)); } } #endif // INCLUDE_ALL_GCS // Always call even when there are not JVMTI environments yet, since environments // may be attached late and JVMTI must track phases of VM execution JvmtiExport::enter_live_phase(); // Signal Dispatcher needs to be started before VMInit event is posted os::signal_init(); // Start Attach Listener if +StartAttachListener or it can't be started lazily if (!DisableAttachMechanism) { AttachListener::vm_start(); if (StartAttachListener || AttachListener::init_at_startup()) { AttachListener::init(); } } // Launch -Xrun agents // Must be done in the JVMTI live phase so that for backward compatibility the JDWP // back-end can launch with -Xdebug -Xrunjdwp. if (!EagerXrunInit && Arguments::init_libraries_at_startup()) { create_vm_init_libraries(); } // Notify JVMTI agents that VM initialization is complete - nop if no agents. JvmtiExport::post_vm_initialized(); if (TRACE_START() != JNI_OK) { vm_exit_during_initialization("Failed to start tracing backend."); } if (CleanChunkPoolAsync) { Chunk::start_chunk_pool_cleaner_task(); } // initialize compiler(s) #if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK) CompileBroker::compilation_init(); #endif if (EnableInvokeDynamic) { // Pre-initialize some JSR292 core classes to avoid deadlock during class loading. // It is done after compilers are initialized, because otherwise compilations of // signature polymorphic MH intrinsics can be missed // (see SystemDictionary::find_method_handle_intrinsic). initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0); initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0); initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0); } #if INCLUDE_MANAGEMENT Management::initialize(THREAD); #endif // INCLUDE_MANAGEMENT if (HAS_PENDING_EXCEPTION) { // management agent fails to start possibly due to // configuration problem and is responsible for printing // stack trace if appropriate. Simply exit VM. vm_exit(1); } if (Arguments::has_profile()) FlatProfiler::engage(main_thread, true); if (MemProfiling) MemProfiler::engage(); StatSampler::engage(); if (CheckJNICalls) JniPeriodicChecker::engage(); BiasedLocking::init(); if (JDK_Version::current().post_vm_init_hook_enabled()) { call_postVMInitHook(THREAD); // The Java side of PostVMInitHook.run must deal with all // exceptions and provide means of diagnosis. if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; } } { MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag); // Make sure the watcher thread can be started by WatcherThread::start() // or by dynamic enrollment. WatcherThread::make_startable(); // Start up the WatcherThread if there are any periodic tasks // NOTE: All PeriodicTasks should be registered by now. If they // aren't, late joiners might appear to start slowly (we might // take a while to process their first tick). if (PeriodicTask::num_tasks() > 0) { WatcherThread::start(); } } // Give os specific code one last chance to start os::init_3(); create_vm_timer.end(); #ifdef ASSERT _vm_complete = true; #endif return JNI_OK; }
以上,就是vm建立的框架程式碼,也已經這麼複雜了。大體有這麼幾個步驟:
- 1. 檢查jdk版本號, 不支援則退出;
- 2. 輸出流初始化;
- 3. sun.java.launcher屬性配置檢查接入;
- 4. 初始化一些系統模組,如隨機數...;
- 5. 初始化系統屬性如java.ext.dirs...;
- 6. 引數解析;
- 7. 系統頁初始化;
- 8. 再初始化更多平臺相關的系統模組, 如執行緒,頁設定,mmap,PV機制,maxfd,優先順序等;
- 9. ThreadLocalStorage初始化;
- 10. 記憶體跟蹤器初始化;
- 11. agentlib 初始化;
- 12. 全域性變數初始化;
- 13. 建立JavaThread, 初始化資訊;
- 14. 將java執行緒對映到系統執行緒JavaThread -> OSThread;
- 15. ObjectMonitor物件監視器建立初始化;
- 16. 進入多執行緒模式;
- 17. 初始化java的全域性模組,如bytecode,classloader...;
- 18. 新增main_thread到執行緒表中;
- 19. 建立 VMThread 執行緒, 啟動vmThread執行緒並等待其事務處理完成;
- 20. JvmtiExport開始執行, 保證agent開始切入;
- 21. 初始化java系統類庫,如system,string...;
- 22. 初始化編譯器compiler;
- 23. jni函式資訊設定;
- 24. jdwp除錯模組執行;
- 25. AttachListener啟動執行;
- 26. BiasedLocking 初始化;
- 27. WatcherThread 啟動;
- 28. PeriodicTask 任務執行;
- 29. 執行完成, 返回建立成功;
細節就不說了(哈哈,因為說也說不清楚)。 我們只需理解流程即可,真正想理解,那麼就需要指定一個特定的小點,來進行探討了。以後再說咯!