JVM系列(一):jvm启动过程速览
jvm是java的核心运行平台,自然是个非常复杂的系统。当然了,说jvm是个平台,实际上也是个泛称。准确的说,它是一个java虚拟机的统称,它并不指具体的某个虚拟机。所以,谈到java虚拟机时,往往我们通常说的都是一些规范性质的东西。
那么,如果想要研究jvm是如何工作的,就不能是泛泛而谈了。我们必须要具体到某个指定的虚拟机实现,以便说清其过程。
1. 说说openjdk
因为java实际上已经被oracle控制,而oracle本身是个商业公司,所以从某种程度上说,这里的java并不是完全开源的。我们称官方的jdk为oraclejdk. 或者叫 hotspot vm.
与此同时,社区维护了一个完全开源的版本,openjdk。这两个jdk实际上,大部分是相同的,只是维护的进度不太一样,以及版权归属不一样。
所以,如果想研究jvm的实现,那么基于openjdk来做,是比较明智的选择。
如果想了解openjdk是如何设计的,以及它有什么高级特性,以及各种最佳实践,那么买一本书是最佳选择。
如果业有余力,想去了解了解源码的,那么可以到官网查看源码。openjdk8的源码地址为: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/ 因为是国外网站的原因,速度不会很快。所以只是在网站上查看源码,还是有点累的。另外,没有ide的帮助,估计很少有人能够坚持下去。另外的下载地址,大家可以网上搜索下,资源总是有的,国人链接速度快。多花点心思找找。
当然要说明的一点是:一个没有设计背景,没有框架概念的源码阅读,都是耍流氓。那样的工作,就像是空中楼阁,并不让人踏实。
2. 谈谈C语言
C语言,一般作为我们的大学入门语言,或多或少都接触过。但要说精通,可能就是很少一部分人了。但我要说的是,只要学过C语言,对于大部分的程序阅读,基本上就不是问题了。
openjdk的实现中,其核心的一部分就是使用C语言写的,当然其他很多语言也是一样的。所以,C语言相当重要,在底层的世界里。这里只是说它重要,但并不代表它就一定最厉害,即不是写C语言的GG就比写JAVA的JJ厉害了。因为,工作不分高低,语言同样。只是各有所长罢了。重点不是在这里,在于思想。
C语言的编程几大流程:写代码(最核心)、编译、链接(最麻烦)、运行。
当然,最核心的自然是写代码。不对,最核心的是:做设计。
C语言中,以一个main()函数为入口,编写各种逻辑后,通过调用和控制main()方法,实现各种复杂逻辑。
所以,要研究一个项目,首先就是要找到其入口。然后根据目的,再进行各功能实现的通路学习。
C语言有极其灵活的语法,超级复杂的指针设计,以及各类似面向对象思想的结构体,以及随时可能与操作系统通信获取信息的能力(各种链接)。所以,导致C语言有时确实比较难以读懂。这也是没办法的事,会很容易,精却很难。这是亘古不变的道理。是一个选择题,也是一道应用题。
一句话,会一点,就够吃瓜群众使用了。
3. openjdk的入口
上面说到,要研究一个C项目,首要就是找到其入口。那么,openjdk的入口在哪呢?
是在 share/bin/main.c 中,main()方法就是其入口。这个文件命名,够清晰了吧,明眼人一看就知道了。哈哈,不过一般地,我们还是需要通过查资料才知晓。
main.c是jvm的唯一main方法入口,其中,jdk被编译出来之后,会有许多的工作箱,如jmap,jps,jstack.... 这些工具箱的入口,实际也是这个main, 只是它们包含了不同的子模块,从而达到不同工具的目的。
main.c的内容也不多,主要它也只是一个框架,为屏蔽各系统的差异。它的存在,主要是为引入 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 <crtassem.h> #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. */ // 定义入口函数,JAVAW模式下使用 WinMain(), 否则使用 main() #ifdef JAVAW char **__initenv; int WINAPI WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow) { int margc; char** margv; const jboolean const_javaw = JNI_TRUE; __initenv = _environ; #else /* JAVAW */ int main(int argc, char **argv) { int margc; char** margv; const jboolean const_javaw = JNI_FALSE; #endif /* JAVAW */ #ifdef _WIN32 // windows下的参数获取 { 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 end margv = (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平台上的参数,直接取自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 <other-options> xx.xx , 或者是 java -jar <other-options> xx.jar 。 具体怎么用无所谓,重点是我们都是用 java这个应用程序启动的虚拟机。因此,我们便知道 java 程序,是我们启动jvm的核心开关。
4.0. jvm启动流程框架
废话不多说,java.c, 是我们要研究的重要文件。它将是一个控制启动流程的实现超人。而它的入口,就是在main()中的定义 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,即加载java程序开始,应用表演时间到 return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret); }
以上就是整个jvm虚拟机的启动过程框架了,基本上跑不掉几个点,就是解析命令行参数,设置参数到某范围内或者环境变量中。加载必要模块,传递变量存储。初始化系统。解析用户系统实现。当然一般地,就是会实现系统主循环,这个动作是由使用系统完成的,jvm只负责执行即可。
因为我们只是想了解大概,所以不以为然,只是其中任何一个点都足够研究很久很久了。抛开那些不说,捡个芝麻先。需要明白:懂得许多的道理却依然过不好这一生。只能安心做个吃瓜群众。
下面,就一些细节点,我们可以视兴趣,稍微深入了解下!
4.1. jre版本选择过程
以上框架中,几个重要的节点,我们可以再细化下实现。细节就不说,太复杂。首先,就是如何确定当前系统使用的jre版本,这很重要,它决定了应用系统是否可以运行的问题。因为有时候,系统的使用者并非开发者,一定存在正确的jre版本。没有jre的环境,所有java执行就会是一句空谈。
// 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; }
逻辑也不复杂,大概就是,解析参数,读取manifest文件,jre版本校验,加载jre以便确认是否存在,最后将相关环境变量放置好。
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). */ int JLI_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); else return (-2); }
manifest中只解析 几个特定的值,写多了也没用。
4.2. 加载VM模块
加载VM是非常重要的一个工作。它是一个平台相关的实现,我们看下 windows版本的实现吧。
// share/windows/bin/java_md.c /* * Load a jvm from "jvmpath" and initialize the invocation functions. */ jboolean LoadJavaVM(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文件实现 /* 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 中,以便后续实用。
4.3. 解析参数信息
通过参数解析,我们就可以如何设置参数了。更深层次的理解。
// 实际就是语法规范 /* * 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 jboolean ParseArguments(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. */ void SetJavaCommandLineProp(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); } // 设置 classpath static void SetClassPath(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 expansion return; 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 选项,保存各参数配置。
/* * Adds a new VM option with the given given name and value. */ void AddOption(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.c 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); } /* * Displays the splash screen according to the jar file name * and image file names stored in environment variables */ void ShowSplashScreen() { 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); } 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; 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是通过一个新线程去运行应用系统的。在将执行控制权交由java代码后,它的主要作用,就是不停地接收命令,执行命令。从而变成一个真正的执行机器。
火车已开,后续更精彩。