##剖析HotSpot的启动
###介绍
HotSpot属于OpenJDK项目的一个功能子集,HotSpot目录下四大子目录:
- agent:包含Serviceability Agent的客户端的实现
- make:用于build出HotSpot的各种配置文件
- src:包括HotSpot的所有源码
- test:单元测试
###Launcher启动器
Launcher是用于启动JVM进程的启动器,有两种,
- 一种windows平台下运行时会保留在控制台
- 一种用于执行Java的GUI程序,不会显示任何程序的输出信息
Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和windows平台下的jvm.dll动态链接库。
###Launcher的执行过程
Launcher是一种用于启动JVM进程的启动器,是一个封装了虚拟机的执行外壳,他负责装载JRE环境和Windows平台下的jvm.dll动态链接库(linux平台下则是libjvm.so,并不是虚拟机的实现)。 在一个JVM的进程内部,只能执行一个指定的Java程序,也就是当执行 多个java程序时,就启动了多个JVM进程。Launcher是JVM的启动器 ,那么必然会由他负责调用HotSpot的核心代码对JVM执行初始化,以及由他负责维护JVM的整个生命周期。具体流程如下所示:
- 进入Launcher的启动函数main()。(main函数主要负责创建运行环境,以及启动一个全新的线程去执行JVM的初始化和调用java程序的main方法)
- 启动一个全新的线程调用JavaMain()函数(负责调用InitializeJVM()函数,但InitializeJVM()函数本身并不具备初始化JVM的能力,而是由他调用本地函数JNI_InitializeJVM()函数去完成真正意义上的JVM初始化)。
- JVM初始化完成后,Launcher调用LoadClass()函数和GetStaticMethodId()函数,分别获取Java程序的启动类和启动方法。
- Launcher会调用本地函数jni_CallStaticVoidMethod()执行java程序的main()方法。
- 最后Launcher调用本地函数jni_DetachCurrentThread()断开与主线程的连接。(断开与主线程的连接之后,Launcher会一直等待程序中所有的非守护线程(non_daemonthread)全部执行结束)
- 调用本地函数jni_DestoryJavaVM()对JVM执行销毁。
启动入口在/home/openjdk/openjdk-jdk8u-master/jdk/src/share/bin/main.c:97,查看代码可知其核心启动方法是JLI_Launch
#include "defines.h"
int main(int argc, char **argv)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
margc = argc;
margv = argv;
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);
}
JLI_Launch函数的代码如下所示:
/*
* Shared source for 'java' command line tool.
*
* If JAVA_ARGS is defined, then acts as a launcher for applications. For
* instance, the JDK command line tools such as javac and javadoc (see
* makefiles for more details) are built with this program. Any arguments
* prefixed with '-J' will be passed directly to the 'java' command.
*/
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);
}
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();
}
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();
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
简言之,JLI_Launch函数主要做了以下事情:
- 将参数保存到全局变量,如版本号、文件名、是否定义了java参数(即JAVA_ARGS宏是否定义)等;
- InitLauncher函数设置调试开关,如果环境变量_JAVA_LAUNCHER_DEBUG有定义则开启Launcher的调试模式,后续调用JLI_IsTraceLauncher函数会返回真(即值1);
- SelectVersion函数解析-version:release、-jre-restrict-search、-no-jre-restrict-search和-splash:imgname 选项,确保运行适当版本的JRE(注意不要将JRE的版本与JVM的数据模型搞混)。需要的JRE版本既可以从命令行选项-version:release指定,也可以在jar包的META-INF/MANIFEST.MF文件中用JRE-Version键指定;
- CreateExecutionEnvironment函数为后续创建虚拟机选择了数据模型,去除-d32、-J-d32、-d64和-J-d64选项;
- LoadJavaVM函数从JVM动态库获取函数指针;
- 解析其他命令行选项;
- 设置额外的虚拟机启动选项;
- 初始化JVM。
其主要流程如下:
-
SelectVersion,从jar包中manifest文件或者命令行读取用户使用的JDK版本,判断当前版本是否合适
-
CreateExecutionEnvironment,设置执行环境参数
-
LoadJavaVM,加载libjvm动态链接库,从中获取JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs三个函数的实现,其中JNI_CreateJavaVM是JVM初始化的核心入口,具体实现在hotspot目录中
jdk/src/java.base/unix/native/libjli/java_md_solinux.c
加载libjvm.so,绑定InvocationFunctions
jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){ //
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
dlsym(libjvm, "JNI_GetCreatedJavaVMs");
return JNI_TRUE;
}
-
ParseArguments 解析命令行参数,如-version,-help等参数在该方法中解析的
-
SetJavaCommandLineProp 解析形如-Dsun.java.command=的命令行参数
-
SetJavaLauncherPlatformProps 解析形如-Dsun.java.launcher.*的命令行参数
/*
* Parse command line options; if the return value of
* ParseArguments is false, the program should exit.
*/
if (!ParseArguments(&argc, &argv, &jarfile, &classname, &ret, jvmpath)) {
exit(ret);
}
/* Override class path if -jar flag was specified */
if (jarfile != 0) {
SetClassPath(jarfile);
}
/* set the -Dsun.java.command pseudo property */
SetJavaCommandLineProp(classname, jarfile, argc, argv);
/* Set the -Dsun.java.launcher pseudo property */
SetJavaLauncherProp();
/* set the -Dsun.java.launcher.* platform properties */
SetJavaLauncherPlatformProps();
#ifndef GAMMA
/* Show the splash screen if needed */
ShowSplashScreen();
#endif
/*
* Done with all command line processing and potential re-execs so
* clean up the environment.
*/
(void)UnsetEnv(ENV_ENTRY);
#ifndef GAMMA
(void)UnsetEnv(SPLASH_FILE_ENV_ENTRY);
(void)UnsetEnv(SPLASH_JAR_ENV_ENTRY);
JLI_MemFree(splash_jar_entry);
JLI_MemFree(splash_file_entry);
#endif
/*
* 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.
*/
命令行参数被解析后,虚拟机启动选项已经构造完毕,JLI_Launch函数最后调用JVMInit函数在新的线程中初始化虚拟机。
- JVMInit,通过JVMInit->ContinueInNewThread->ContinueInNewThread0->pthread_create创建了一个新的线程,执行JavaMain函数,主线程pthread_join该线程,在JavaMain函数中完成虚拟机的初始化和启动。
JVMInit函数定义在文件jdk/src/solaris/bin/java_md_solinux.c中,相关代码如下:
int 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);
}
ifn结构体保存了先前用LoadJavaVM函数在JVM动态库中查找到的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs函数指针。
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;
}
}
ContinueInNewThread函数的参数分别是:
- ifn保存了JVM动态库的函数指针;
- argc和argv分别是传递给第一个操作数的参数个数和参数列表;
- mode是启动模式,从类启动还是从jar启动;
- what是第一个操作数。
int ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size);
}
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)tmp;
} else {
/*
* Continue execution in current thread if for some reason (e.g. out of
* memory/LWP) a new thread can't be created. This will likely fail
* later in continuation as JNI_CreateJavaVM needs to create quite a
* few new threads, anyway, just give it a try..
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
return rslt;
}
在ContinueInNewThread0中,父线程首先利用pthread库函数创建新线程执行JavaMain函数的代码,然后调用pthread_join函数将自己阻塞。
####在主线程中执行JavaMain()函数
当main()函数完成运行环境的创建后,接下来就任务交给在主线程中执行JavaMain()函数,JavaMain()函数会从Main()函数中传递过来的一些变量参数,然后初始化几个比较重要的局部变量
//从Main()函数中传递过来的一些变量参数
struct JavaMainArgs *args = (struct JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
char *jarfile = args->jarfile;
char *classname = args->classname;
InvocationFunctions ifn = args->ifn;
//初始化几个比较重要的局部变量
JavaVM *vm = 0;
JNIEnv *env = 0;
jstring mainClassName;
jclass mainClass;
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;
结构体JavaMainArgs如下
struct JavaMainArgs {
int argc;
char ** argv;
char * jarfile;
char * classname;
InvocationFunctions ifn;
};
结构体JavaMainArgs内部的 InvocationFunctions类型中包含的函数指针与对应的目标函数,如下:
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
函数指针所指向的目标函数主要用于完成断开主线程和执行JVM销毁等功能,如下:
(*vm)->DetachCurrentThread(vm)
(*vm)->DestroyJavaVM(vm)
当函数指针成功指向目标函数和JVM初始化后,Launcher会执行LoadClass()函数和GetStaticMethodID()函数
执行JavaMain函数
int JNICALL JavaMain(void * _args) {
JavaMainArgs *args = (JavaMainArgs *)_args;
......
InvocationFunctions ifn = args->ifn;
//初始化虚拟机
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
......
//加载Hello类
mainClass = LoadMainClass(env, mode, what);
appClass = GetApplicationClass(env);
//获取Hello类的main方法
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
//调用main()方法
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
//结束
LEAVE();
}
接下来,调用CallStaticVoidMethod()执行Java程序的Main()方法,当Java程序执行完后,断开与主线程的连接
if ((*vm)->DetachCurrentThread(vm) != 0) {
message = "Could not detach main thread.";
messageDest = JNI_TRUE;
ret = 1;
goto leave;
}int
当断开连接后,Launcher等待所有非守护线程全部执行完成,最后销毁JVM
(*vm)->DestroyJavaVM(vm);
####JavaMain函数源码解析
int JNICALL JavaMain(void * _args)
{
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;
RegisterThread();
/* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
if (showSettings != NULL) {
ShowSettings(env, showSettings);
CHECK_EXCEPTION_LEAVE(1);
}
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
/* If the user specified neither a class name nor a JAR file */
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}
FreeKnownVMs(); /* after last possible PrintUsage() */
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}
/* At this stage, argc/argv have the application's arguments */
if (JLI_IsTraceLauncher()){
int i;
printf("%s is '%s'\n", launchModeNames[mode], what);
printf("App's argc is %d\n", argc);
for (i=0; i < argc; i++) {
printf(" argv[%2d] = '%s'\n", i, argv[i]);
}
}
ret = 1;
/*
* Get the application's main class.
*
* See bugid 5030265. The Main-Class name has already been parsed
* from the manifest, but not parsed properly for UTF-8 support.
* Hence the code here ignores the value previously extracted and
* uses the pre-existing code to reextract the value. This is
* possibly an end of release cycle expedient. However, it has
* also been discovered that passing some character sets through
* the environment has "strange" behavior on some variants of
* Windows. Hence, maybe the manifest parsing code local to the
* launcher should never be enhanced.
*
* Hence, future work should either:
* 1) Correct the local parsing code and verify that the
* Main-Class attribute gets properly passed through
* all environments,
* 2) Remove the vestages of maintaining main_class through
* the environment (and remove these comments).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* In some cases when launching an application that needs a helper, e.g., a
* JavaFX application with no main method, the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*
* PostJVMInit uses the class name as the application name for GUI purposes,
* for example, on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1);
/*
* The LoadMainClass not only loads the main class, it will also ensure
* that the main method's signature is correct, therefore further checking
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
/*
* The launcher's exit code (in the absence of calls to
* System.exit) will be non-zero if main threw an exception.
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}
ifn结构体保存了先前用LoadJavaVM函数在JVM动态库中查找到的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs函数指针。
####InitializeJVM函数
InitializeJVM函数进一步初始化JVM,其代码如下:
/*
* Initializes the Java Virtual Machine. Also frees options array when
* finished.
*/
static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
JavaVMInitArgs args;
jint r;
memset(&args, 0, sizeof(args));
args.version = JNI_VERSION_1_2;
args.nOptions = numOptions;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
if (JLI_IsTraceLauncher()) {
int i = 0;
printf("JavaVM args:\n ");
printf("version 0x%08lx, ", (long)args.version);
printf("ignoreUnrecognized is %s, ",
args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
printf("nOptions is %ld\n", (long)args.nOptions);
for (i = 0; i < numOptions; i++)
printf(" option[%2d] = '%s'\n",
i, args.options[i].optionString);
}
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
return r == JNI_OK;
}
args结构体表示JVM启动选项,全局变量options指向先前TranslateApplicationArgs函数和ParseArguments函数添加或解析的JVM启动选项,另一个全局变量numOptions则保存了选项个数;ifn结构体的CreateJavaVM函数指针即指向JVM动态库中的JNI_CreateJavaVM函数。
####JNI_CreateJavaVM函数
JNI_CreateJavaVM函数定义在文件hotspot/src/share/vm/prims/jni.cpp中,预处理后的代码如下所示:
JNIIMPORT jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
jint result = (-1);
if (Atomic::xchg(1, &vm_created) == 1) {
return (-5);
}
if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
return (-1);
}
bool can_try_again = true;
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == 0) {
JavaThread *thread = JavaThread::current();
*vm = (JavaVM *)(&main_vm);
*(JNIEnv**)penv = thread->jni_environment();
RuntimeService::record_application_start();
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_start(thread);
}
EventThreadStart event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
event.commit();
}
if (CompileTheWorld) ClassLoader::compile_the_world();
if (ReplayCompiles) ciReplay::replay(thread);
test_error_handler();
execute_internal_vm_tests();
ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
} else {
if (can_try_again) {
safe_to_recreate_vm = 1;
}
*vm = 0;
*(JNIEnv**)penv = 0;
OrderAccess::release_store(&vm_created, 0);
}
return result;
}
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
.....
//TLS初始化
ThreadLocalStorage::init();
// 输入输出流初始化
ostream_init();
//os模块初始化
os::init();
os::init_before_ergo();
//VM初始化开始
HOTSPOT_VM_INIT_BEGIN();
//初始化全局数据结构并在堆中创建系统类
vm_init_globals();
//Attach线程
JavaThread* main_thread = new JavaThread();
main_thread->set_thread_state(_thread_in_vm);
main_thread->initialize_thread_current();
// 线程栈基和栈大小
main_thread->record_stack_base_and_size();
main_thread->set_active_handles(JNIHandleBlock::allocate_block());
// Enable guard page *after* os::create_main_thread(), otherwise it would
// crash Linux VM, see notes in os_linux.cpp.
main_thread->create_stack_guard_pages();
//初始化Java级同步子系统
ObjectMonitor::Initialize();
// 初始化全局模块
jint status = init_globals();
// Should be done after the heap is fully created
main_thread->cache_global_variables();
//初始化java/lang下的类
initialize_java_lang_classes(main_thread, CHECK_JNI_ERR);
//标记初始化完成
set_init_completed();
//元空间初始化
Metaspace::post_initialize();
HOTSPOT_VM_INIT_END();
// 预先初始化一些JSR292核心类,String,System,Class类
initialize_jsr292_core_classes(CHECK_JNI_ERR);
// 这将初始化模块系统。只有java.base类可以是加载到阶段2完成
call_initPhase2(CHECK_JNI_ERR);
// 即使还没有JVMTI环境,也要始终调用,因为环境可能会延迟连接,而且JVMTI必须跟踪VM执行的各个阶段
JvmtiExport::enter_start_phase();
// Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
JvmtiExport::post_vm_start();
//最终系统初始化,包括安全管理器和系统类加载器
call_initPhase3(CHECK_JNI_ERR);
// 缓存系统类加载器
SystemDictionary::compute_java_system_loader(CHECK_(JNI_ERR));
//即使还没有JVMTI环境,也要始终调用,因为环境可能会延迟连接,而且JVMTI必须跟踪VM执行的各个阶段
JvmtiExport::enter_live_phase();
//通知初始化完成
JvmtiExport::post_vm_initialized();
......
#ifdef ASSERT
_vm_complete = true;
#endif
return JNI_OK; //JVM初始化完成
}
hotspot/src/share/vm/runtime/init.cpp
初始化全局数据结构并在堆中创建系统类
void vm_init_globals() {
check_ThreadShadow();
basic_types_init(); //heapOopSize与压缩指针
eventlog_init(); //一些事件
mutex_init();// 全局监视器和互斥体
chunkpool_init();//四个ChunkPool
perfMemory_init();//监控内存初始化
SuspendibleThreadSet_init();
}
hotspot/src/share/vm/runtime/init.cpp
初始化全局模块
jint init_globals() {
HandleMark hm;
management_init(); //监控服务,线程服务,运行时服务,类加载服务初始化
bytecodes_init(); // 字节码解释器初始化
classLoader_init1(); //类加载器初始化第一阶段:zip,jimage入口,设置启动类路径
compilationPolicy_init();//编译策略,根据编译等级确定从c1,c2编译器
codeCache_init();//代码缓存初始化
VM_Version_init();
os_init_globals();
stubRoutines_init1();//例程初始化第一阶段,例程:代码调用点
//堆空间,元空间,AOTLoader,SymbolTable,StringTable,G1收集器初始化
jint status = universe_init();
if (status != JNI_OK)
return status;
interpreter_init(); // 模板解释器初始化
invocationCounter_init(); //热点统计初始化
marksweep_init();//标记清除GC初始化
accessFlags_init();
templateTable_init();//模板表初始化
InterfaceSupport_init();
SharedRuntime::generate_stubs();//生成部分例程入口
universe2_init(); // vmSymbols,系统字典,预加载类,构建基本数据对象模型
referenceProcessor_init();//对象引用策略
jni_handles_init();//JNI引用句柄
#if INCLUDE_VM_STRUCTS
vmStructs_init(); //一些相关数据结构
#endif // INCLUDE_VM_STRUCTS
vtableStubs_init();//虚表例程初始化
InlineCacheBuffer_init();//内联缓冲初始化
compilerOracle_init();//Oracle相关的初始化
dependencyContext_init();//监控统计相关
if (!compileBroker_init()) {
return JNI_EINVAL;
}
VMRegImpl::set_regName(); //虚拟机寄存器初始化
if (!universe_post_init()) {//初始化必要的类,注册堆空间到内存服务
return JNI_ERR;
}
javaClasses_init(); //java基础类相关计算
stubRoutines_init2(); // 例程初始化第二阶段,模板解释器继承体系初始化
MethodHandles::generate_adapters();//方法与代码缓存点适配
return JNI_OK;
}
HotSpot的初始化过程中集中体现在init_globals()中各模块的初始化,从最基础的监控,服务,类加载器初始化为后续各阶段的初始化做准备。后续包括解释器,类加载器,堆空间,编译器,收集器等各模块逐一初始化为HotSpot的运行做好准备。
OpenJDK总结了JNI_CreateJavaVM函数的工作:
- 保证同一时间只有一个线程调用此方法,并且同一进程中只能创建一个虚拟机实例。请注意一旦到达了不可返回点(point of no return),虚拟机就不能被再次创建,这是因为虚拟机创建的静态数据结构不能被重新初始化;
- 检查JNI版本是否被支持,为GC日志初始化输出流,初始化OS模块如随机数生成器、高分辨率时间、内存页大小和保护页(guard page)等;
- 解析并保存传入的参数和属性供后续使用,初始化标准的Java系统属性;
- OS模块被进一步初始化,根据解析的参数和属性初始化同步机制、栈、内存和safepoint page。此时其他库如libzip、libhpi、libjava和libthread被加载,初始化并设置信号处理函数和线程库;
- 初始化输出流,初始化和启动需要的Agent库如hprof、jdi等。
- 初始化线程状态和线程局部存储(TLS);
- 初始化全局数据,如事件日志、OS同步原语等;
- 此时开始创建线程,Java里的main线程被创建并被附加(attach)到当前OS线程,然而这个线程还没有被加入线程链表。初始化并启用Java层的同步机制;
- 初始化其他全局模块如BootClassLoader、CodeCache、Interpreter、Compiler、JNI、SystemDictionary和Universe,此时到达了不可返回点,不可以在相同进程的地址空间创建另一个虚拟机了;
- 将main线程加入线程链表。执行虚拟机关键功能的VMThread被创建;
- 初始化并加载Java类,如java.lang.String、java.lang.System、java.lang.Thread、java.lang.ThreadGroup、java.lang.reflect.Method、java.lang.ref.Finalizer、java.lang.Class和System。此时虚拟机已被初始化并可运行,但还不是完全可用;
- 启动信号处理线程,编译器被初始化,启动CompileBroker线程。其他辅助线程如StatSampler和WatcherThread也被启动,此时虚拟机已经完全可用,JNIEnv接口指针被填充并返回给调用者,虚拟机已经准备好服务新的JNI请求了。
至此HotSpot虚拟机初始化完毕,将返回java.c的JavaMain函数继续往下加载Hello主类并找到Hello类的静态main方法并执行,执行完毕后LEAVE(),退出。
LoadMainClass函数
TODO
GetApplicationClass函数
TODO
PostJVMInit函数
TODO
其他工作
TODO