深入理解Java之jvm啟動(dòng)流程
jvm是java的核心運(yùn)行平臺(tái),自然是個(gè)非常復(fù)雜的系統(tǒng)。當(dāng)然了,說(shuō)jvm是個(gè)平臺(tái),實(shí)際上也是個(gè)泛稱。準(zhǔn)確的說(shuō),它是一個(gè)java虛擬機(jī)的統(tǒng)稱,它并不指具體的某個(gè)虛擬機(jī)。所以,談到j(luò)ava虛擬機(jī)時(shí),往往我們通常說(shuō)的都是一些規(guī)范性質(zhì)的東西。
那么,如果想要研究jvm是如何工作的,就不能是泛泛而談了。我們必須要具體到某個(gè)指定的虛擬機(jī)實(shí)現(xiàn),以便說(shuō)清其過(guò)程。
1. 說(shuō)說(shuō)openjdk
因?yàn)閖ava實(shí)際上已經(jīng)被oracle控制,而oracle本身是個(gè)商業(yè)公司,所以從某種程度上說(shuō),這里的java并不是完全開(kāi)源的。我們稱官方的jdk為oraclejdk. 或者叫 hotspot vm
與此同時(shí),社區(qū)維護(hù)了一個(gè)完全開(kāi)源的版本,openjdk。這兩個(gè)jdk實(shí)際上,大部分是相同的,只是維護(hù)的進(jìn)度不太一樣,以及版權(quán)歸屬不一樣。
所以,如果想研究jvm的實(shí)現(xiàn),那么基于openjdk來(lái)做,是比較明智的選擇。
如果想了解openjdk是如何設(shè)計(jì)的,以及它有什么高級(jí)特性,以及各種最佳實(shí)踐,那么買(mǎi)一本書(shū)是最佳選擇。
如果業(yè)有余力,想去了解了解源碼的,那么可以到官網(wǎng)查看源碼。openjdk8的源碼地址為: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/ 因?yàn)槭菄?guó)外網(wǎng)站的原因,速度不會(huì)很快。所以只是在網(wǎng)站上查看源碼,還是有點(diǎn)累的。另外,沒(méi)有ide的幫助,估計(jì)很少有人能夠堅(jiān)持下去。另外的下載地址,大家可以網(wǎng)上搜索下,資源總是有的,國(guó)人鏈接速度快。多花點(diǎn)心思找找。
當(dāng)然要說(shuō)明的一點(diǎn)是:一個(gè)沒(méi)有設(shè)計(jì)背景,沒(méi)有框架概念的源碼閱讀,都是而流氓。那樣的工作,就像是空中樓閣,并不讓人踏實(shí)。
2. 談?wù)凜語(yǔ)言
C語(yǔ)言,一般作為我們的大學(xué)入門(mén)語(yǔ)言,或多或少都接觸過(guò)。但要說(shuō)精通,可能就是很少一部分人了。但我要說(shuō)的是,只要學(xué)過(guò)C語(yǔ)言,對(duì)于大部分的程序閱讀,基本上就不是問(wèn)題了。
openjdk的實(shí)現(xiàn)中,其核心的一部分就是使用C語(yǔ)言寫(xiě)的,當(dāng)然其他很多語(yǔ)言也是一樣的。所以,C語(yǔ)言相當(dāng)重要,在底層的世界里。這里只是說(shuō)它重要,但并不代表它就一定最厲害,即不是寫(xiě)C語(yǔ)言的GG就比寫(xiě)JAVA的JJ厲害了。因?yàn)?,工作不分高低,語(yǔ)言同樣。只是各有所長(zhǎng)罷了。重點(diǎn)不是在這里,在于思想。
C語(yǔ)言的編程幾大流程:寫(xiě)代碼(最核心)、編譯、鏈接(最麻煩)、運(yùn)行。
當(dāng)然,最核心的自然是寫(xiě)代碼。不對(duì),最核心的是:做設(shè)計(jì)。
C語(yǔ)言中,以一個(gè)main()函數(shù)為入口,編寫(xiě)各種邏輯后,通過(guò)調(diào)用和控制main()方法,實(shí)現(xiàn)各種復(fù)雜邏輯。
所以,要研究一個(gè)項(xiàng)目,首先就是要找到其入口。然后根據(jù)目的,再進(jìn)行各功能實(shí)現(xiàn)的通路學(xué)習(xí)。
C語(yǔ)言有極其靈活的語(yǔ)法,超級(jí)復(fù)雜的指針設(shè)計(jì),以及各類(lèi)似面向?qū)ο笏枷氲慕Y(jié)構(gòu)體,以及隨時(shí)可能操作系統(tǒng)獲取信息的能力(各種鏈接)。所以,導(dǎo)致C語(yǔ)言有時(shí)確實(shí)比較難以讀懂。這也是沒(méi)辦法的事,會(huì)很容易,精卻很難。這是亙古不變的道理。是一個(gè)選擇題,也是一道應(yīng)用題。
一句話,會(huì)一點(diǎn),就夠吃瓜群眾使用了。
3. openjdk的入口
上面說(shuō)到,要研究一個(gè)C項(xiàng)目,首要就是找到其入口。那么,openjdk的入口在哪呢?
是在 share/bin/main.c 中,main()方法就是其入口。這個(gè)文件命名,夠清晰了吧,明眼人一看就知道了。哈哈,不過(guò)一般地,我們還是需要通過(guò)查資料才知曉。
main.c是jvm的唯一main方法入口,其中,jdk被編譯出來(lái)之后,會(huì)有許多的工作箱,如jmap,jps,jstack.... 這些工具箱的入口,實(shí)際也是這個(gè)main, 只是它們包含了不同的子模塊,從而達(dá)到不同工具的目的。
main.c的內(nèi)容也不多,主要它也只是一個(gè)框架,為屏蔽各系統(tǒng)的差異。它的存在,主要是為引入 JLI_LAUNCH() 方法,相當(dāng)于定義自己的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.
*/
// 定義入口函數(shù),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下的參數(shù)獲取
{
int i = 0;
if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
printf("Windows original main args:\n");
for (i = 0 ; i < __argc ; i++) {
printf("wwwd_args[%d] = %s\n", i, __argv[i]);
}
}
}
JLI_CmdToArgs(GetCommandLine());
margc = JLI_GetStdArgc();
// add one more to mark the 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平臺(tái)上的參數(shù),直接取自main入?yún)?
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);
}
因?yàn)閖ava語(yǔ)言被設(shè)計(jì)成跨平臺(tái)的語(yǔ)言,那么如何跨平臺(tái)呢?因?yàn)槠脚_(tái)差異總是存在的,如果語(yǔ)言本身不關(guān)注平臺(tái),那么自然是有人在背后關(guān)注了平臺(tái),從而屏蔽掉了差異。是了,這就是虛擬機(jī)存在的意義。因此,在入口方法,我們就可以看到,它一上來(lái)就關(guān)注平臺(tái)差異性。這是必須的。
4. openjdk的啟動(dòng)流程
有了上面的入口知識(shí),好像是明白了一些道理。但是好像還是沒(méi)有達(dá)到要理解啟動(dòng)過(guò)程的目的。不急,且聽(tīng)我慢慢道來(lái)。
我們啟動(dòng)一個(gè)虛擬機(jī)時(shí),一般是使用 java -classpath:xxx <other-options> xx.xx , 或者是 java -jar <other-options> xx.jar 。 具體怎么用無(wú)所謂,重點(diǎn)是我們都是 java這個(gè)應(yīng)用程序啟動(dòng)的虛擬機(jī)。因此,我們便知道 java 程序,是我們啟動(dòng)jvm的核心開(kāi)關(guān)。
4.0. jvm啟動(dòng)流程框架
廢話不多說(shuō),java.c, 是我們要研究的重要文件。它將是一個(gè)控制啟動(dòng)流程的實(shí)現(xiàn)超人。而它的入口,就是在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;
// 初始化啟動(dòng)器
InitLauncher(javaw);
// 打印狀態(tài)
DumpState();
// 跟蹤調(diào)用啟動(dòng)
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
/*
* Make sure the specified version of the JRE is running.
*
* There are three things to note about the SelectVersion() routine:
* 1) If the version running isn't correct, this routine doesn't
* return (either the correct version has been exec'd or an error
* was issued).
* 2) Argc and Argv in this scope are *not* altered by this routine.
* It is the responsibility of subsequent code to ignore the
* arguments handled by this routine.
* 3) As a side-effect, the variable "main_class" is guaranteed to
* be set (if it should ever be set). This isn't exactly the
* poster child for structured programming, but it is a small
* price to pay for not processing a jar file operand twice.
* (Note: This side effect has been disabled. See comment on
* bugid 5030265 below.)
*/
// 解析命令行參數(shù),選擇一jre版本
SelectVersion(argc, argv, &main_class);
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
if (!IsJavaArgs()) {
// 設(shè)置一些特殊的環(huán)境變量
SetJvmEnvironment(argc,argv);
}
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
if (JLI_IsTraceLauncher()) {
start = CounterGet(); // 記錄啟動(dòng)時(shí)間
}
// 加載VM, 重中之重
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
if (JLI_IsTraceLauncher()) {
end = CounterGet();
}
JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
(long)(jint)Counter2Micros(end-start));
++argv;
--argc;
// 解析更多參數(shù)信息
if (IsJavaArgs()) {
/* Preprocess wrapper arguments */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* Set default CLASSPATH */
cpath = getenv("CLASSPATH");
if (cpath == NULL) {
cpath = ".";
}
SetClassPath(cpath);
}
/* Parse command line options; if the return value of
* ParseArguments is false, the program should exit.
*/
// 解析參數(shù)
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
{
return(ret);
}
/* Override class path if -jar flag was specified */
if (mode == LM_JAR) {
SetClassPath(what); /* Override class path */
}
/* set the -Dsun.java.command pseudo property */
SetJavaCommandLineProp(what, argc, argv);
/* Set the -Dsun.java.launcher pseudo property */
SetJavaLauncherProp();
/* set the -Dsun.java.launcher.* platform properties */
SetJavaLauncherPlatformProps();
// 初始化jvm,即加載java程序開(kāi)始,應(yīng)用表演時(shí)間到
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
以上就是整個(gè)jvm虛擬機(jī)的啟動(dòng)過(guò)程框架了,基本上跑不掉幾個(gè)點(diǎn),就是解析命令行參數(shù),設(shè)置參數(shù)到某范圍內(nèi)或者環(huán)境變量中。加載必要模塊,傳遞變量存儲(chǔ)。初始化系統(tǒng)。解析用戶系統(tǒng)實(shí)現(xiàn)。當(dāng)然一般地,就是會(huì)實(shí)現(xiàn)系統(tǒng)主循環(huán),這個(gè)動(dòng)作是由使用系統(tǒng)完成的,jvm只負(fù)責(zé)執(zhí)行即可。
因?yàn)槲覀冎皇窍肓私獯蟾?,所以不以為然,只是其中任何一個(gè)點(diǎn)都足夠研究很久很久了。拋開(kāi)那些不說(shuō),撿個(gè)芝麻先。需要明白:懂得許多的道理卻依然過(guò)不好這一生。只能安心做個(gè)吃瓜群眾。
下面,就一些細(xì)節(jié)點(diǎn),我們可以視興趣,稍微深入了解下!
4.1. jre版本選擇過(guò)程
以上框架中,幾個(gè)重要的節(jié)點(diǎn),我們可以再細(xì)化下實(shí)現(xiàn)。細(xì)節(jié)就不說(shuō),太復(fù)雜。首先,就是如何確定當(dāng)前系統(tǒng)使用的jre版本,這很重要,它決定了應(yīng)用系統(tǒng)是否可以運(yùn)行的問(wèn)題。因?yàn)橛袝r(shí)候,系統(tǒng)的使用者并非開(kāi)發(fā)者,一定存在正確的jre版本。沒(méi)有jre的環(huán)境,所有java執(zhí)行就會(huì)是一句空談。
// 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;
}
邏輯也不復(fù)雜,大概就是,解析參數(shù),讀取manifest文件,jre版本校驗(yàn),加載jre以便確認(rèn)是否存在,最后將相關(guān)環(huán)境變量放置好。
4.2. 加載VM模塊
加載VM是非常重要的一個(gè)工作。它是一個(gè)平臺(tái)相關(guān)的實(shí)現(xiàn),我們看下 windows版本的實(shí)現(xiàn)吧。
// 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 中是通過(guò)路徑加載dll文件實(shí)現(xiàn)
/* Load the Java VM DLL */
if ((handle = LoadLibrary(jvmpath)) == 0) {
JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath);
return JNI_FALSE;
}
/* Now get the function addresses */
// 獲取虛擬機(jī)操作內(nèi)存地址
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;
}
可見(jiàn),最重要的工作是被封裝到 JRE 中的,應(yīng)用層面只是調(diào)用JRE的方法即可。在windows中通過(guò)加載msvcrt模塊完成工作,然后抽取vm的兩個(gè)方法簽名到ifn中,以便后續(xù)實(shí)用。
4.3. 解析參數(shù)信息
通過(guò)參數(shù)解析,我們就可以如何設(shè)置參數(shù)了。更深層次的理解。
// 實(shí)際就是語(yǔ)法規(guī)范
/*
* Parses command line arguments. Returns JNI_FALSE if launcher
* should exit without starting vm, returns JNI_TRUE if vm needs
* to be started to process given options. *pret (the launcher
* process return value) is set to 0 for a normal exit.
*/
static 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);
}
// 設(shè)置 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 ... 然后解析出來(lái)。最后通過(guò)AddOption()存儲(chǔ)起來(lái)。
4.4. jvm初始化
好像我們一直討論的都是這個(gè),但是實(shí)際上里面還有一個(gè)真正的jvm的初始化過(guò)程。這里方才會(huì)接入真正的java程序,也才大家所關(guān)心的地方。
// 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;
}
}
看起來(lái),jvm是通過(guò)一個(gè)新線程去運(yùn)行應(yīng)用系統(tǒng)的。在將執(zhí)行控制權(quán)交由java代碼后,它的主要作用,就是不停地接收命令,執(zhí)行命令。從而變成一個(gè)真正的執(zhí)行機(jī)器。
到此這篇關(guān)于深入理解Java之jvm啟動(dòng)流程的文章就介紹到這了,更多相關(guān)Java之jvm啟動(dòng)流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot整合redis過(guò)期key監(jiān)聽(tīng)實(shí)現(xiàn)訂單過(guò)期的項(xiàng)目實(shí)踐
現(xiàn)在各種電商平臺(tái)都有自己的訂單過(guò)期時(shí)間設(shè)置,那么如何設(shè)置訂單時(shí)間過(guò)期呢,本文主要介紹了springboot整合redis過(guò)期key監(jiān)聽(tīng)實(shí)現(xiàn)訂單過(guò)期的項(xiàng)目實(shí)踐,感興趣的可以了解一下2023-12-12
Shiro安全框架的主要組件及認(rèn)證過(guò)程簡(jiǎn)介
這篇文章主要介紹了Shiro安全框架的主要組件及認(rèn)證過(guò)程簡(jiǎn)介,Shiro?是一個(gè)強(qiáng)大靈活的開(kāi)源安全框架,可以完全處理身份驗(yàn)證、授權(quán)、加密和會(huì)話管理,本文就來(lái)介紹一下此框架的核心組成,需要的朋友可以參考下2023-08-08
springboot集成mybatisplus的詳細(xì)步驟
MyBatis-Plus (opens new window)(簡(jiǎn)稱 MP)是一個(gè) MyBatis (opens new window)的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開(kāi)發(fā)、提高效率而生,這篇文章主要介紹了springboot四步集成mybatisplus,需要的朋友可以參考下2022-10-10
java教程散列表和樹(shù)所對(duì)應(yīng)容器類(lèi)及HashMap解決沖突學(xué)習(xí)
本篇篇文章是java教程,主要介紹了java教程散列表,樹(shù)所對(duì)應(yīng)容器類(lèi)及HashMap解決沖突的學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10
java公眾平臺(tái)通用接口工具類(lèi)HttpConnectUtil實(shí)例代碼
下面小編就為大家分享一篇java公眾平臺(tái)通用接口工具類(lèi)HttpConnectUtil實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
基于Springboot的高校社團(tuán)管理系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)
本文將基于Springboot+Mybatis開(kāi)發(fā)實(shí)現(xiàn)一個(gè)高校社團(tuán)管理系統(tǒng),系統(tǒng)包含三個(gè)角色:管理員、團(tuán)長(zhǎng)、會(huì)員。文中采用的技術(shù)有Springboot、Mybatis、Jquery、AjAX、JSP等,感興趣的可以了解一下2022-07-07

