深入理解Java之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以便确认是否存在,最后将相关环境变量放置好。

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()存储起来。

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代码后,它的主要作用,就是不停地接收命令,执行命令。从而变成一个真正的执行机器。

到此这篇关于深入理解Java之jvm启动流程的文章就介绍到这了,更多相关Java之jvm启动流程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java应用/JVM宕机排查步骤操作

    相信大家都遇到过,自己的Java应用运行一段时间就宕机了或者响应请求特别慢.这时候就需要我们了来找出问题所在了.绝大部分都是代码问题导致的. 一.服务宕机 如果是服务宕机,发生致命问题导致进程已经死掉了,那么已经访问不了了,通常都是CPU问题引起的,程序一般会自己生成javacore文件,一般生成位置在/root目录或jar包同目录下.JavaCore文件主要保存的是Java应用各线程在某一时刻的运行的位置,即JVM执行到哪一个类.哪一个方法.哪一个行上. 找到这个文件,执行命令 gdb jav

  • java编译器和JVM的区别

    Java虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行.java编译器把java编译成字节码,也就是.class文件,然后JVM给编译成的字节码提供运行环境.java的源代码是无法直接在JVM上运行的. 1.java编译器 Java语言写的源程序通过Java编译器,编译成与平台无关的'字节码程序'(.class文件,也就是0,1二进制程序),然后在OS之上的Java解释器中解释执行. 也相

  • JVM---jstack分析Java线程CPU占用,线程死锁的解决

    本文章主要演示在Windows环境,Linux环境也差不多. 一.分析CPU占用飙高 首先写一个Java程序,并模拟一个死循环.让CPU使用率飙高.CPU负载过大的话,新的请求就处理不了了,这就是很多程序变慢了甚至不能访问的原因之一. 下面是我这里的Controller,启动程序之后,开多个请求访问这个方法.死循环代码就不贴了,自己构造.我这里模拟的一个截取字符串的死循环. /** * 演示死循环导致cpu使用率飙高 * */ @RequestMapping("/loop") publ

  • JVM类运行机制实现原理解析

    1.一段java程序是如何运行起来的呢? Java源文件,通过编译器,产生.Class字节码文件,字节码文件通过Java虚拟机中的解释器,编译成特定及其上的机器码,那Java虚拟机又是怎样加载java程序并执行起来的呢? 简单来说:通过类加载器加载字节码文件,被分配到JVM的运行时数据区的字节码会被执行引擎执行. (1)类加载器,加载.class文件 (2)运行数据区:栈区.堆区.PC寄存器.本地方法栈.方法区 (3)执行引擎:执行包在装载类方法中的指令 2. 类加载器 类的加载是指将类的.cl

  • 图解JVM内存模型

    前言 上篇文章我们一起了解了jvm虚拟机类的加载机制,而且是以一种纯大白话进行的一场闲聊,相信小伙伴们应该印象深刻,感兴趣的小伙伴可以重温一下上一篇文章大白话谈JVM的类加载机制. 当jvm加载了类后,会把需要使用的对象放入到内存当中,那么jvm的内存模型是什么样的呢? 今天我们就来探索一下jvm的内存模型.由于有小伙伴反映想加些图更容易理解,王子接下来的文章打算用更多的图例来讲解. 方法区 很多小伙伴之前也了解过jvm的内存模型,知道有方法区这个东西,但可能了解的不是很详细. 其实方法区是在J

  • 深入理解Java之jvm启动流程

    jvm是java的核心运行平台,自然是个非常复杂的系统.当然了,说jvm是个平台,实际上也是个泛称.准确的说,它是一个java虚拟机的统称,它并不指具体的某个虚拟机.所以,谈到java虚拟机时,往往我们通常说的都是一些规范性质的东西. 那么,如果想要研究jvm是如何工作的,就不能是泛泛而谈了.我们必须要具体到某个指定的虚拟机实现,以便说清其过程. 1. 说说openjdk 因为java实际上已经被oracle控制,而oracle本身是个商业公司,所以从某种程度上说,这里的java并不是完全开源的

  • 深入理解Java虚拟机 JVM 内存结构

    目录 前言 JVM是什么 JVM内存结构概览 运行时数据区 程序计数器 Java虚拟机栈 本地方法栈 方法区 运行时常量池 Java堆 直接内存 前言 JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我们面对OutOfMemoryError时不再一脸懵逼,可以用掌握的JVM知识去查找分析问题.去进行JVM的调优.去让我们的应用程序可以支持更高的并发量等......总之一句话,学好JVM很重要! JVM是什么 J

  • 深入理解Java虚拟机_动力节点Java学院整理

    什么是Java虚拟机 Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到的比较靠谱的解释: 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行. 这种解释应该算是正确的,但是只描述了虚拟机的外部行为和功能,并没有针对内部原理

  • 快速理解Java垃圾回收和jvm中的stw

    Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外).Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互:这些现象多半是由于gc引起. GC时的Stop the World(STW)是大家最大的敌人.但可能很多人还不清楚,除了GC,JVM下还会发生停顿现象. JVM里有一条特殊的线程--VM Threads,专门用来执行一些特殊的VM Operation

  • 使用Java visualVM监控远程JVM的流程分析

    VisualVM是JDK自带的一款全能型性能监控和故障分析工具,包括对CPU使用.JVM堆内存消耗.线程.类加载的实时监控,内存dump文件分析,垃圾回收运行情况的可视化分析等,对故障排查和性能调优很有帮助. 1 监控远程tomcat 对于被监控的tomcat,需要修改tomcat的bin目录下的Catalina.sh配置文件,增加如下配置. JAVA_ OPTS="$JAVA_ OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.j

  • Java 图解Spring启动时的后置处理器工作流程是怎样的

    探究Spring的后置处理器 本次我们主要探究invokeBeanFactoryPostProcessors():后面的代码下次再做解析: 入口代码refresh() AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // ...... applicationContext.refresh(); public void refresh() throws

  • 深入理解Java虚拟机体系结构

    1概述 众所周知,Java支持平台无关性.安全性和网络移动性.而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么.正是得益于Java虚拟机,它号称的"一次编译,到处运行"才能有所保障. 1.1Java程序执行流程 Java程序的执行依赖于编译环境和运行环境.源码代码转变成可执行的机器代码,由下面的流程完成: Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行.Java程序的运行需要Java虚拟

  • Java虚拟机JVM性能优化(二):编译器

    本文将是JVM 性能优化系列的第二篇文章(第一篇:传送门),Java 编译器将是本文讨论的核心内容. 本文中,作者(Eva Andreasson)首先介绍了不同种类的编译器,并对客户端编译,服务器端编译器和多层编译的运行性能进行了对比.然后,在文章的最后介绍了几种常见的JVM优化方法,如死代码消除,代码嵌入以及循环体优化. Java最引以为豪的特性"平台独立性"正是源于Java编译器.软件开发人员尽其所能写出最好的java应用程序,紧接着后台运行的编译器产生高效的基于目标平台的可执行代

  • 解析Java的JVM以及类与对象的概念

    Java虚拟机(JVM)以及跨平台原理 相信大家已经了解到Java具有跨平台的特性,可以"一次编译,到处运行",在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的. 那么,跨平台是怎样实现的呢?这就要谈及Java虚拟机(Java Virtual Machine,简称 JVM). JVM也是一个软件,不同的平台有不同的版本.我们编写的Java源码,编译后会生成一种 .class 文件,称为字节码文件.Java虚拟机就是负责将字节码文件翻译成特定平

  • 深入理解Java反射

    要想理解反射的原理,首先要了解什么是类型信息.Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息:另一种是反射机制,它允许我们在运行时发现和使用类的信息. 1.Class对象 理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息.Class对象就是用来创建所有"常规"对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似

随机推荐