JVM系列(一):jvm啟動過程速覽
jvm是java的核心運行平臺,自然是個非常復雜的系統(tǒng)。當然了,說jvm是個平臺,實際上也是個泛稱。準確的說,它是一個java虛擬機的統(tǒng)稱,它并不指具體的某個虛擬機。所以,談到java虛擬機時,往往我們通常說的都是一些規(guī)范性質的東西。
那么,如果想要研究jvm是如何工作的,就不能是泛泛而談了。我們必須要具體到某個指定的虛擬機實現(xiàn),以便說清其過程。
1:說說openjdk
因為java實際上已經被oracle控制,而oracle本身是個商業(yè)公司,所以從某種程度上說,這里的java并不是完全開源的。我們稱官方的jdk為oraclejdk. 或者叫 hotspot vm.
與此同時,社區(qū)維護了一個完全開源的版本,openjdk。這兩個jdk實際上,大部分是相同的,只是維護的進度不太一樣,以及版權歸屬不一樣。
所以,如果想研究jvm的實現(xiàn),那么基于openjdk來做,是比較明智的選擇。
如果想了解openjdk是如何設計的,以及它有什么高級特性,以及各種最佳實踐,那么買一本書是最佳選擇。
如果業(yè)有余力,想去了解了解源碼的,那么可以到官網查看源碼。openjdk8的源碼地址為:?http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/? ?因為是國外網站的原因,速度不會很快。所以只是在網站上查看源碼,還是有點累的。另外,沒有ide的幫助,估計很少有人能夠堅持下去。另外的下載地址,大家可以網上搜索下,資源總是有的,國人鏈接速度快。多花點心思找找。
當然要說明的一點是:一個沒有設計背景,沒有框架概念的源碼閱讀,都是而流氓。那樣的工作,就像是空中樓閣,并不讓人踏實。
2. 談談C語言
C語言,一般作為我們的大學入門語言,或多或少都接觸過。但要說精通,可能就是很少一部分人了。但我要說的是,只要學過C語言,對于大部分的程序閱讀,基本上就不是問題了。
openjdk的實現(xiàn)中,其核心的一部分就是使用C語言寫的,當然其他很多語言也是一樣的。所以,C語言相當重要,在底層的世界里。這里只是說它重要,但并不代表它就一定最厲害,即不是寫C語言的GG就比寫JAVA的JJ厲害了。因為,工作不分高低,語言同樣。只是各有所長罷了。重點不是在這里,在于思想。
C語言的編程幾大流程:寫代碼(最核心)、編譯、鏈接(最麻煩)、運行。
當然,最核心的自然是寫代碼。不對,最核心的是:做設計。
C語言中,以一個main()函數(shù)為入口,編寫各種邏輯后,通過調用和控制main()方法,實現(xiàn)各種復雜邏輯。
所以,要研究一個項目,首先就是要找到其入口。然后根據(jù)目的,再進行各功能實現(xiàn)的通路學習。
C語言有極其靈活的語法,超級復雜的指針設計,以及各類似面向對象思想的結構體,以及隨時可能操作系統(tǒng)獲取信息的能力(各種鏈接)。所以,導致C語言有時確實比較難以讀懂。這也是沒辦法的事,會很容易,精卻很難。這是亙古不變的道理。是一個選擇題,也是一道應用題。
一句話,會一點,就夠吃瓜群眾使用了。
3. openjdk的入口
上面說到,要研究一個C項目,首要就是找到其入口。那么,openjdk的入口在哪呢?
是在 share/bin/main.c 中,main()方法就是其入口。這個文件命名,夠清晰了吧,明眼人一看就知道了。哈哈,不過一般地,我們還是需要通過查資料才知曉。
main.c是jvm的唯一main方法入口,其中,jdk被編譯出來之后,會有許多的工作箱,如jmap,jps,jstack.... 這些工具箱的入口,實際也是這個main, 只是它們包含了不同的子模塊,從而達到不同工具的目的。
main.c的內容也不多,主要它也只是一個框架,為屏蔽各系統(tǒng)的差異。它的存在,主要是為引入 JLI_LAUNCH() 方法,相當于定義自己的main()方法。
/** This file contains the main entry point into the launcher code* this is the only file which will be repeatedly compiled by other* tools. The rest of the files will be linked in.*/#include "defines.h"#ifdef _MSC_VER#if _MSC_VER > 1400 && _MSC_VER < 1600/** When building for Microsoft Windows, main has a dependency on msvcr??.dll.** When using Visual Studio 2005 or 2008, that must be recorded in* the [java,javaw].exe.manifest file.** As of VS2010 (ver=1600), the runtimes again no longer need manifests.** Reference:* C:/Program Files/Microsoft SDKs/Windows/v6.1/include/crtdefs.h*/#include#ifdef _M_IX86#pragma comment(linker,"/manifestdependency:\"type='win32' " \"name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".CRT' " \"version='" _CRT_ASSEMBLY_VERSION "' " \"processorArchitecture='x86' " \"publicKeyToken='" _VC_ASSEMBLY_PUBLICKEYTOKEN "'\"")#endif /* _M_IX86 *///This may not be necessary yet for the Windows 64-bit build, but it//will be when that build environment is updated. Need to test to see//if it is harmless:#ifdef _M_AMD64#pragma comment(linker,"/manifestdependency:\"type='win32' " \"name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".CRT' " \"version='" _CRT_ASSEMBLY_VERSION "' " \"processorArchitecture='amd64' " \"publicKeyToken='" _VC_ASSEMBLY_PUBLICKEYTOKEN "'\"")#endif /* _M_AMD64 */#endif /* _MSC_VER > 1400 && _MSC_VER < 1600 */#endif /* _MSC_VER *//** Entry point.*/// 定義入口函數(shù),JAVAW模式下使用 WinMain(), 否則使用 main()#ifdef JAVAWchar **__initenv;int WINAPIWinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow){int margc;char** margv;const jboolean const_javaw = JNI_TRUE;__initenv = _environ;#else /* JAVAW */intmain(int argc, char **argv){int margc;char** margv;const jboolean const_javaw = JNI_FALSE;#endif /* JAVAW */#ifdef _WIN32// windows下的參數(shù)獲取{int i = 0;if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {printf("Windows original main args:\n");for (i = 0 ; i < __argc ; i++) {printf("wwwd_args[%d] = %s\n", i, __argv[i]);}}}JLI_CmdToArgs(GetCommandLine());margc = JLI_GetStdArgc();// add one more to mark the endmargv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));{int i = 0;StdArg *stdargs = JLI_GetStdArgs();for (i = 0 ; i < margc ; i++) {margv[i] = stdargs[i].arg;}margv[i] = NULL;}#else /* *NIXES */// 各種linux平臺上的參數(shù),直接取自main入參margc = argc;margv = argv;#endif /* WIN32 */// 核心: 重新定義入口方法為: JLI_Launch()return JLI_Launch(margc, margv,sizeof(const_jargs) / sizeof(char *), const_jargs,sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,FULL_VERSION,DOT_VERSION,(const_progname != NULL) ? const_progname : *margv,(const_launcher != NULL) ? const_launcher : *margv,(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,const_cpwildcard, const_javaw, const_ergo_class);}
因為java語言被設計成跨平臺的語言,那么如何跨平臺呢?因為平臺差異總是存在的,如果語言本身不關注平臺,那么自然是有人在背后關注了平臺,從而屏蔽掉了差異。是了,這就是虛擬機存在的意義。因此,在入口方法,我們就可以看到,它一上來就關注平臺差異性。這是必須的。
4. openjdk的啟動流程
有了上面的入口知識,好像是明白了一些道理。但是好像還是沒有達到要理解啟動過程的目的。不急,且聽我慢慢道來。
我們啟動一個虛擬機時,一般是使用? java -classpath:xxx
4.0. jvm啟動流程框架
廢話不多說,java.c, 是我們要研究的重要文件。它將是一個控制啟動流程的實現(xiàn)超人。而它的入口,就是在main()中的定義 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);// 打印狀態(tài)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.)*/// 解析命令行參數(shù),選擇一jre版本SelectVersion(argc, argv, &main_class);CreateExecutionEnvironment(&argc, &argv,jrepath, sizeof(jrepath),jvmpath, sizeof(jvmpath),jvmcfg, sizeof(jvmcfg));if (!IsJavaArgs()) {// 設置一些特殊的環(huán)境變量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;// 解析更多參數(shù)信息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.*/// 解析參數(shù)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,即加載java程序開始,應用表演時間到return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);}復制代碼以上就是整個jvm虛擬機的啟動過程框架了,基本上跑不掉幾個點,就是解析命令行參數(shù),設置參數(shù)到某范圍內或者環(huán)境變量中。加載必要模塊,傳遞變量存儲。初始化系統(tǒng)。解析用戶系統(tǒng)實現(xiàn)。當然一般地,就是會實現(xiàn)系統(tǒng)主循環(huán),這個動作是由使用系統(tǒng)完成的,jvm只負責執(zhí)行即可。因為我們只是想了解大概,所以不以為然,只是其中任何一個點都足夠研究很久很久了。拋開那些不說,撿個芝麻先。需要明白:懂得許多的道理卻依然過不好這一生。只能安心做個吃瓜群眾。下面,就一些細節(jié)點,我們可以視興趣,稍微深入了解下!4.1. jre版本選擇過程以上框架中,幾個重要的節(jié)點,我們可以再細化下實現(xiàn)。細節(jié)就不說,太復雜。首先,就是如何確定當前系統(tǒng)使用的jre版本,這很重要,它決定了應用系統(tǒng)是否可以運行的問題。因為有時候,系統(tǒng)的使用者并非開發(fā)者,一定存在正確的jre版本。沒有jre的環(huán)境,所有java執(zhí)行就會是一句空談。復制代碼// 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;}
邏輯也不復雜,大概就是,解析參數(shù),讀取manifest文件,jre版本校驗,加載jre以便確認是否存在,最后將相關環(huán)境變量放置好。
manifest解析過程:
// share/bin/parse_manifest.c/** Read the manifest from the specified jar file and fill in the manifest_info* structure with the information found within.** Error returns are as follows:* 0 Success* -1 Unable to open jarfile* -2 Error accessing the manifest from within the jarfile (most likely* a manifest is not present, or this isn't a valid zip/jar file).*/intJLI_ParseManifest(char *jarfile, manifest_info *info){int fd;zentry entry;char *lp;char *name;char *value;int rc;char *splashscreen_name = NULL;if ((fd = open(jarfile, O_RDONLY#ifdef O_LARGEFILE| O_LARGEFILE /* large file mode */#endif#ifdef O_BINARY| O_BINARY /* use binary mode on windows */#endif)) == -1) {return (-1);}info->manifest_version = NULL;info->main_class = NULL;info->jre_version = NULL;info->jre_restrict_search = 0;info->splashscreen_image_file_name = NULL;// *manifest_name = "META-INF/MANIFEST.MF";if (rc = find_file(fd, &entry, manifest_name) != 0) {close(fd);return (-2);}manifest = inflate_file(fd, &entry, NULL);if (manifest == NULL) {close(fd);return (-2);}lp = manifest;while ((rc = parse_nv_pair(&lp, &name, &value)) > 0) {if (JLI_StrCaseCmp(name, "Manifest-Version") == 0)info->manifest_version = value;else if (JLI_StrCaseCmp(name, "Main-Class") == 0)info->main_class = value;else if (JLI_StrCaseCmp(name, "JRE-Version") == 0)info->jre_version = value;else if (JLI_StrCaseCmp(name, "JRE-Restrict-Search") == 0) {if (JLI_StrCaseCmp(value, "true") == 0)info->jre_restrict_search = 1;} else if (JLI_StrCaseCmp(name, "Splashscreen-Image") == 0) {info->splashscreen_image_file_name = value;}}close(fd);if (rc == 0)return (0);elsereturn (-2);}
manifest中只解析 幾個特定的值,寫多了也沒用。
4.2. 加載VM模塊
加載VM是非常重要的一個工作。它是一個平臺相關的實現(xiàn),我們看下 windows版本的實現(xiàn)吧。
// share/windows/bin/java_md.c/** Load a jvm from "jvmpath" and initialize the invocation functions.*/jbooleanLoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){HINSTANCE handle;JLI_TraceLauncher("JVM path is %s\n", jvmpath);/** The Microsoft C Runtime Library needs to be loaded first. A copy is* assumed to be present in the "JRE path" directory. If it is not found* there (or "JRE path" fails to resolve), skip the explicit load and let* nature take its course, which is likely to be a failure to execute.**/LoadMSVCRT();// windows 中是通過路徑加載dll文件實現(xiàn)/* Load the Java VM DLL */if ((handle = LoadLibrary(jvmpath)) == 0) {JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath);return JNI_FALSE;}/* Now get the function addresses */// 獲取虛擬機操作內存地址ifn->CreateJavaVM =(void *)GetProcAddress(handle, "JNI_CreateJavaVM");ifn->GetDefaultJavaVMInitArgs =(void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");if (ifn->CreateJavaVM == 0 || ifn->GetDefaultJavaVMInitArgs == 0) {JLI_ReportErrorMessage(JNI_ERROR1, (char *)jvmpath);return JNI_FALSE;}return JNI_TRUE;}
可見,最重要的工作是被封裝到 JRE 中的,應用層面只是調用JRE的方法即可。在windows中通過加載 msvcrt 模塊完成工作,然后抽取vm的兩個方法簽名到 ifn 中,以便后續(xù)實用。
4.3. 解析參數(shù)信息
通過參數(shù)解析,我們就可以如何設置參數(shù)了。更深層次的理解。
// 實際就是語法規(guī)范/** Parses command line arguments. Returns JNI_FALSE if launcher* should exit without starting vm, returns JNI_TRUE if vm needs* to be started to process given options. *pret (the launcher* process return value) is set to 0 for a normal exit.*/static jbooleanParseArguments(int *pargc, char ***pargv,int *pmode, char **pwhat,int *pret, const char *jrepath){int argc = *pargc;char **argv = *pargv;int mode = LM_UNKNOWN;char *arg;*pret = 0;while ((arg = *argv) != 0 && *arg == '-') {argv++; --argc;if (JLI_StrCmp(arg, "-classpath") == 0 || JLI_StrCmp(arg, "-cp") == 0) {ARG_CHECK (argc, ARG_ERROR1, arg);SetClassPath(*argv);mode = LM_CLASS;argv++; --argc;} else if (JLI_StrCmp(arg, "-jar") == 0) {ARG_CHECK (argc, ARG_ERROR2, arg);mode = LM_JAR;} else if (JLI_StrCmp(arg, "-help") == 0 ||JLI_StrCmp(arg, "-h") == 0 ||JLI_StrCmp(arg, "-?") == 0) {printUsage = JNI_TRUE;return JNI_TRUE;} else if (JLI_StrCmp(arg, "-version") == 0) {printVersion = JNI_TRUE;return JNI_TRUE;} else if (JLI_StrCmp(arg, "-showversion") == 0) {showVersion = JNI_TRUE;} else if (JLI_StrCmp(arg, "-X") == 0) {printXUsage = JNI_TRUE;return JNI_TRUE;/** The following case checks for -XshowSettings OR -XshowSetting:SUBOPT.* In the latter case, any SUBOPT value not recognized will default to "all"*/} else if (JLI_StrCmp(arg, "-XshowSettings") == 0 ||JLI_StrCCmp(arg, "-XshowSettings:") == 0) {showSettings = arg;} else if (JLI_StrCmp(arg, "-Xdiag") == 0) {AddOption("-Dsun.java.launcher.diag=true", NULL);/** The following case provide backward compatibility with old-style* command line options.*/} else if (JLI_StrCmp(arg, "-fullversion") == 0) {JLI_ReportMessage("%s full version \"%s\"", _launcher_name, GetFullVersion());return JNI_FALSE;} else if (JLI_StrCmp(arg, "-verbosegc") == 0) {AddOption("-verbose:gc", NULL);} else if (JLI_StrCmp(arg, "-t") == 0) {AddOption("-Xt", NULL);} else if (JLI_StrCmp(arg, "-tm") == 0) {AddOption("-Xtm", NULL);} else if (JLI_StrCmp(arg, "-debug") == 0) {AddOption("-Xdebug", NULL);} else if (JLI_StrCmp(arg, "-noclassgc") == 0) {AddOption("-Xnoclassgc", NULL);} else if (JLI_StrCmp(arg, "-Xfuture") == 0) {AddOption("-Xverify:all", NULL);} else if (JLI_StrCmp(arg, "-verify") == 0) {AddOption("-Xverify:all", NULL);} else if (JLI_StrCmp(arg, "-verifyremote") == 0) {AddOption("-Xverify:remote", NULL);} else if (JLI_StrCmp(arg, "-noverify") == 0) {AddOption("-Xverify:none", NULL);} else if (JLI_StrCCmp(arg, "-prof") == 0) {char *p = arg + 5;char *tmp = JLI_MemAlloc(JLI_StrLen(arg) + 50);if (*p) {sprintf(tmp, "-Xrunhprof:cpu=old,file=%s", p + 1);} else {sprintf(tmp, "-Xrunhprof:cpu=old,file=java.prof");}AddOption(tmp, NULL);} else if (JLI_StrCCmp(arg, "-ss") == 0 ||JLI_StrCCmp(arg, "-oss") == 0 ||JLI_StrCCmp(arg, "-ms") == 0 ||JLI_StrCCmp(arg, "-mx") == 0) {char *tmp = JLI_MemAlloc(JLI_StrLen(arg) + 6);sprintf(tmp, "-X%s", arg + 1); /* skip '-' */AddOption(tmp, NULL);} else if (JLI_StrCmp(arg, "-checksource") == 0 ||JLI_StrCmp(arg, "-cs") == 0 ||JLI_StrCmp(arg, "-noasyncgc") == 0) {/* No longer supported */JLI_ReportErrorMessage(ARG_WARN, arg);} else if (JLI_StrCCmp(arg, "-version:") == 0 ||JLI_StrCmp(arg, "-no-jre-restrict-search") == 0 ||JLI_StrCmp(arg, "-jre-restrict-search") == 0 ||JLI_StrCCmp(arg, "-splash:") == 0) {; /* Ignore machine independent options already handled */} else if (ProcessPlatformOption(arg)) {; /* Processing of platform dependent options */} else if (RemovableOption(arg)) {; /* Do not pass option to vm. */} else {AddOption(arg, NULL);}}if (--argc >= 0) {*pwhat = *argv++;}if (*pwhat == NULL) {*pret = 1;} else if (mode == LM_UNKNOWN) {/* default to LM_CLASS if -jar and -cp option are* not specified */mode = LM_CLASS;}if (argc >= 0) {*pargc = argc;*pargv = argv;}*pmode = mode;return JNI_TRUE;}/** inject the -Dsun.java.command pseudo property into the args structure* this pseudo property is used in the HotSpot VM to expose the* Java class name and arguments to the main method to the VM. The* HotSpot VM uses this pseudo property to store the Java class name* (or jar file name) and the arguments to the class's main method* to the instrumentation memory region. The sun.java.command pseudo* property is not exported by HotSpot to the Java layer.*/voidSetJavaCommandLineProp(char *what, int argc, char **argv){int i = 0;size_t len = 0;char* javaCommand = NULL;char* dashDstr = "-Dsun.java.command=";if (what == NULL) {/* unexpected, one of these should be set. just return without* setting the property*/return;}/* determine the amount of memory to allocate assuming* the individual components will be space separated*/len = JLI_StrLen(what);for (i = 0; i < argc; i++) {len += JLI_StrLen(argv[i]) + 1;}/* allocate the memory */javaCommand = (char*) JLI_MemAlloc(len + JLI_StrLen(dashDstr) + 1);/* build the -D string */*javaCommand = '\0';JLI_StrCat(javaCommand, dashDstr);JLI_StrCat(javaCommand, what);for (i = 0; i < argc; i++) {/* the components of the string are space separated. In* the case of embedded white space, the relationship of* the white space separated components to their true* positional arguments will be ambiguous. This issue may* be addressed in a future release.*/JLI_StrCat(javaCommand, " ");JLI_StrCat(javaCommand, argv[i]);}AddOption(javaCommand, NULL);}// 設置 classpathstatic voidSetClassPath(const char *s){char *def;const char *orig = s;static const char format[] = "-Djava.class.path=%s";/** usually we should not get a null pointer, but there are cases where* we might just get one, in which case we simply ignore it, and let the* caller deal with it*/if (s == NULL)return;s = JLI_WildcardExpandClasspath(s);if (sizeof(format) - 2 + JLI_StrLen(s) < JLI_StrLen(s))// s is corrupted after wildcard expansionreturn;def = JLI_MemAlloc(sizeof(format)- 2 /* strlen("%s") */+ JLI_StrLen(s));sprintf(def, format, s);AddOption(def, NULL);if (s != orig)JLI_MemFree((char *) s);}
-Xxxxx, --xxx 格式配置,如 -Xms1024G, --noclassgc ... ?然后解析出來。?最后通過 AddOption()?存儲起來。在AddOption() 通過一個全局的 options 選項,保存各參數(shù)配置。
/** Adds a new VM option with the given given name and value.*/voidAddOption(char *str, void *info){/** Expand options array if needed to accommodate at least one more* VM option. 以2倍的形式進行擴容。*/if (numOptions >= maxOptions) {if (options == 0) {maxOptions = 4;options = JLI_MemAlloc(maxOptions * sizeof(JavaVMOption));} else {JavaVMOption *tmp;maxOptions *= 2;tmp = JLI_MemAlloc(maxOptions * sizeof(JavaVMOption));memcpy(tmp, options, numOptions * sizeof(JavaVMOption));JLI_MemFree(options);options = tmp;}}options[numOptions].optionString = str;options[numOptions++].extraInfo = info;if (JLI_StrCCmp(str, "-Xss") == 0) {jlong tmp;if (parse_size(str + 4, &tmp)) {threadStackSize = tmp;}}if (JLI_StrCCmp(str, "-Xmx") == 0) {jlong tmp;if (parse_size(str + 4, &tmp)) {maxHeapSize = tmp;}}if (JLI_StrCCmp(str, "-Xms") == 0) {jlong tmp;if (parse_size(str + 4, &tmp)) {initialHeapSize = tmp;}}}
從以上,我們也可以得知一個事情,即當設置了多次的堆大小 -Xmx, -Xms 時,會以最后一個設置為準。
4.4. jvm初始化
好像我們一直討論的都是這個,但是實際上里面還有一個真正的jvm的初始化過程。這里方才會接入真正的java程序,也才大家所關心的地方。
// java.cJVMInit(InvocationFunctions* ifn, jlong threadStackSize,int argc, char **argv,int mode, char *what, int ret){ShowSplashScreen();return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);}/** Displays the splash screen according to the jar file name* and image file names stored in environment variables*/voidShowSplashScreen(){const char *jar_name = getenv(SPLASH_JAR_ENV_ENTRY);const char *file_name = getenv(SPLASH_FILE_ENV_ENTRY);int data_size;void *image_data = NULL;float scale_factor = 1;char *scaled_splash_name = NULL;if (file_name == NULL){return;}scaled_splash_name = DoSplashGetScaledImageName(jar_name, file_name, &scale_factor);if (jar_name) {if (scaled_splash_name) {image_data = JLI_JarUnpackFile(jar_name, scaled_splash_name, &data_size);}if (!image_data) {scale_factor = 1;image_data = JLI_JarUnpackFile(jar_name, file_name, &data_size);}if (image_data) {DoSplashInit();DoSplashSetScaleFactor(scale_factor);DoSplashLoadMemory(image_data, data_size);JLI_MemFree(image_data);}} else {DoSplashInit();if (scaled_splash_name) {DoSplashSetScaleFactor(scale_factor);DoSplashLoadFile(scaled_splash_name);} else {DoSplashLoadFile(file_name);}}if (scaled_splash_name) {JLI_MemFree(scaled_splash_name);}DoSplashSetFileJarName(file_name, jar_name);/** Done with all command line processing and potential re-execs so* clean up the environment.*/(void)UnsetEnv(ENV_ENTRY);(void)UnsetEnv(SPLASH_FILE_ENV_ENTRY);(void)UnsetEnv(SPLASH_JAR_ENV_ENTRY);JLI_MemFree(splash_jar_entry);JLI_MemFree(splash_file_entry);}intContinueInNewThread(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;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;}}
看起來,jvm是通過一個新線程去運行應用系統(tǒng)的。在將執(zhí)行控制權交由java代碼后,它的主要作用,就是不停地接收命令,執(zhí)行命令。從而變成一個真正的執(zhí)行機器。
?火車已開,后續(xù)更精彩。

騰訊、阿里、滴滴后臺面試題匯總總結 — (含答案)
面試:史上最全多線程面試題 !
最新阿里內推Java后端面試題
JVM難學?那是因為你沒認真看完這篇文章

關注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構知識以及最新面試寶典


看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
作者:等你歸去來
出處:https://www.cnblogs.com/yougewe/p/14383351.html
