jvm是java的核心运行平台,自然是个非常复杂的系统。当然了,说jvm是个平台,实际上也是个泛称。准确的说,它是一个java虚拟机的统称,它并不指具体的某个虚拟机。所以,谈到java虚拟机时,往往我们通常说的都是一些规范性质的东西。

那么,如果想要研究jvm是如何工作的,就不能是泛泛而谈了。我们必须要具体到某个指定的虚拟机实现,以便说清其过程。

1. 说说openjdk

因为java实际上已经被oracle控制,而oracle本身是个商业公司,所以从某种程度上说,这里的java并不是完全开源的。我们称官方的jdk为oraclejdk. 或者叫 hotspot vm

与此同时,社区维护了一个完全开源的版本,openjdk。这两个jdk实际上,大部分是相同的,只是维护的进度不太一样,以及版权归属不一样。

所以,如果想研究jvm的实现,那么基于openjdk来做,是比较明智的选择。

如果想了解openjdk是如何设计的,以及它有什么高级特性,以及各种最佳实践,那么买一本书是最佳选择。

如果业有余力,想去了解了解源码的,那么可以到官网查看源码。openjdk8的源码地址为: 因为是国外网站的原因,速度不会很快。所以只是在网站上查看源码,还是有点累的。另外,没有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 != '
// 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;
}
') *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 = '
// 实际就是语法规范
/*
* 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);
}
'; 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启动流程内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!