OpenJdk1.8笔记--java启动流程

本文记录openjdk1.8 启动过程流程梳理。

java启动

Jdk中java的入口函数文件为openjdk\jdk\src\share\bin\main.c中的main方法(window上为WinMain),然后调用jdk8u-dev/jdk/src/share/bin/java.c的JLI_Launch方法,启动一个jvm虚拟机;

main

作用:程序入口
位置:openjdk\jdk\src\share\bin\main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*
* Entry point.
*/
//java虚拟机启动入口函数
#ifdef JAVAW //window平台入口
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 */ //linux平台入口函数
int main(int argc, char **argv){
int margc; //参数个数
char** margv; //命令参数
const jboolean const_javaw = JNI_FALSE;
#endif

#ifdef _WIN32
{
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 */
margc = argc;
margv = argv;
#endif /* WIN32 */

//调用JLI_Launch执行java的执行体逻辑,参考代码jdk8u-dev/jdk/src/share/bin/java.c
return JLI_Launch(margc, //命令参数个数
margv, //参数数组
sizeof(const_jargs) / sizeof(char *), //java args参数个数
const_jargs, //java参数
sizeof(const_appclasspath) / sizeof(char *), //classpath 数量
const_appclasspath, //classpath数量
FULL_VERSION, //完整的版本号
DOT_VERSION, //版本号
(const_progname != NULL) ? const_progname : *margv, //程序名称
(const_launcher != NULL) ? const_launcher : *margv, //启动器名称
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE, //默认为0
const_cpwildcard, //是否支持扩展classpath 默认为true
const_javaw, //window下true, 其他false
const_ergo_class);//运行模式 默认为DEFAULT_POLICY=0 其他包括NEVER_SERVER_CLASS、ALWAYS_SERVER_CLASS
}

JLI_Launch

作用:java入口函数,解析参数、创建环境、加载jvm动态库
位置:jdk8u-dev/jdk/src/share/bin/java.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* Entry point. java的入口函数
*/
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; //启动模式 默认为0 其他参考:java.h 中LaunchMode枚举定义
char *what = NULL;
char *cpath = 0;
char *main_class = NULL; //带有main函数的class文件
int ret;
InvocationFunctions ifn; //函数指针结构体包括:创建jvm、获取jvm启动参数、获取创建的jvm
jlong start, end; //启动结束时间
char jvmpath[MAXPATHLEN]; //jvm路径
char jrepath[MAXPATHLEN]; //jre路径
char jvmcfg[MAXPATHLEN]; //jvm配置

_fVersion = fullversion;
_dVersion = dotversion;
_launcher_name = lname;
_program_name = pname;
_is_java_args = javaargs;
_wc_enabled = cpwildcard;
_ergo_policy = ergo;

//初始化启动器,window下注册启动异常时弹出ui选择调试器
InitLauncher(javaw);
DumpState(); //判断是否定义环境变量_JAVA_LAUNCHER_DEBUG,是的话打印调试信息
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);
}

/*
* 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,可以从jar包中读取META-INF/MAINFEST.MF中获取
//查看mian_class
SelectVersion(argc, argv, &main_class);

//创建执行环境
CreateExecutionEnvironment(&argc,
&argv,
jrepath,
sizeof(jrepath),
jvmpath,
sizeof(jvmpath),
jvmcfg,
sizeof(jvmcfg));

//初始设置函数指针为无效指针
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;

//如果时debug的话,记录启动时间
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}

//加载java vm虚拟机
//加载jvmpath(jdk/lib/amd64/server/libjvm.so),并初始化jvm的指针
/**
* 获取动态库中的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs、JNI_GetCreatedJavaVMs入口地址
* 填充到ifn结构体中
*/
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}

//debug模式下记录加载java vm虚拟机时间
if (JLI_IsTraceLauncher()) {
end = CounterGet();
}

//打印加载javavm时间
JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
(long)(jint)Counter2Micros(end-start));

++argv;
--argc;

//是否有参数
if (IsJavaArgs()) {
/* 解析参数 */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* 设置环境变量中的classpath路径 */
cpath = getenv("CLASSPATH");
if (cpath == NULL) {
cpath = ".";
}
SetClassPath(cpath);
}

/* 解析命令行参数,如果解析失败则程序退出.
*/
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) {
return(ret);
}

/* 如果-jar参数指定的话,重写classpath值 */
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的虚拟机并条用int JNICALL JavaMain(void * _args);运行入口函数
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

JVMInit

作用:显示启动画面,执行虚拟机入口函数
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int
JVMInit(InvocationFunctions* ifn, //函数指针
jlong threadStackSize, //线程栈大小 在AddOption函数中修改全局的threadStackSize变量值,通过-Xss修改
int argc, //参数数目
char **argv, //参数
int mode, //模式
char *what, //
int ret) //返回值
{
//根据存储在环境变量中的jar文件名和镜像文件名显示启动画面
ShowSplashScreen();
//创建线程执行代码逻辑
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}

ContinueInNewThread

作用:组织参数,执行真正的虚拟机入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int
ContinueInNewThread(InvocationFunctions* ifn, //函数指针
jlong threadStackSize, //线程栈大小
int argc,
char **argv,
int mode,
char *what,
int ret)
{

/*
* 如果没有指定线程栈大小, 则会检查vm是否指定一个默认值.
* 注意:虚拟机不再支持1.1,但是他会通过初始化参数返回一个默认的栈大小值.
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1, 0, sizeof(args1_1));
//指定版本号
args1_1.version = JNI_VERSION_1_1;
//通过虚拟机返回一个指定的参数值,该方法时从libjvm.so里面导出的函数指针
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* 忽略返回值 */
if (args1_1.javaStackSize > 0) {
//如果查询到有效值,则会修改全局定义的栈大小
threadStackSize = args1_1.javaStackSize;
}
}

{
/* 创建一个新线程去创建jvm,然后调用main方法*/
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;
}
}

ContinueInNewThread0

作用:尝试创建新线程执行代码逻辑,创建新线程失败则在当前线程执行代码逻辑
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
* 暂停当前线程,然后继续执行一个新的线程
*/
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), //线程入口函数,在这里具体执行的时JavaMain方法
jlong stack_size, //线程栈大小,会使用该值创建线程
void * args) { //参数
int rslt;
#ifdef __linux__ //linux代码块
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); //如果给定的线程栈大小值有效,则设置创建线程的栈大小,否则使用默认值
}

/* 调用线程库创建线程,并设定入口函数为JavaMain */
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp); //创建成功后,并等待线程的结束,主线程挂起
rslt = (int)tmp;
} else {
/*
* 如果因为某些条件导致创建线程失败,则在当前线程执行JavaMain方法
*/
rslt = continuation(args);
}

pthread_attr_destroy(&attr);
#else /* ! __linux__ */ //solaris系统
thread_t tid;
long flags = 0;
if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
void * tmp;
thr_join(tid, NULL, &tmp);
rslt = (int)tmp;
} else {
/* See above. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#endif /* __linux__ */
return rslt;
}

JavaMain

作用:虚拟机的入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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; //当前虚拟机导致的函数指针
//该机制可以保证同一环境配置多个jdk版本

JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL; //main函数class
jclass appClass = NULL; // 正在启动的实际应用程序类
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;

RegisterThread(); //window/类unix为空实现,macos特别处理

/* 初始化虚拟机 */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) { //初始化虚拟机,通过调用libvm.so
//导出的CreateJavaVM执行真正的
//创建虚拟机逻辑,最主要的时把env绑定到具体的jvm上
//后续的操作都是通过JNIEnv实现的
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();
}
}

/* 如果没有指定class或者jar,则直接退出 */
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}

FreeKnownVMs(); /* after last possible PrintUsage() */

/* 记录初始化jvm时间 */
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}

/* 接下来,从argv获取应用的参数,打印参数 */
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.
*/

/* 加载Main class */
mainClass = LoadMainClass(env, mode, what);
/* 检查是否指定main class, 不存在则退出虚拟机*/
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.
*/
/* 获取应用的class文件 */
appClass = GetApplicationClass(env);
/* 检查是否指定app class, 不存在返回-1 */
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.
*/
/*window和类unix为空实现,在macos下设定gui程序的程序名称*/
PostJVMInit(env, appClass, vm);
/*
* 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.
*/
/*从mainclass中加载静态main方法*/
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. */
/* 调用main方法,并把参数传递过去 */
(*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();
}

InitializeJVM

作用:通过libvm.so导出的函数创建虚拟机并和JNIEnv绑定
位置:jdk8u-dev/jdk/src/share/bin/java.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 初始化java虚拟机
*/
static jboolean
InitializeJVM(JavaVM **pvm, //虚拟机指针
JNIEnv **penv, //JNIEnv指针
InvocationFunctions *ifn) //导出的函数指针
{
JavaVMInitArgs args;
jint r;

memset(&args, 0, sizeof(args));
args.version = JNI_VERSION_1_2; //指定jni版本号
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);
}

/* 创建java虚拟机,并和JNIEnv绑定 */
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
/* 释放参数内存 */
JLI_MemFree(options);
/* 返回创建结果 */
return r == JNI_OK;
}

JNI_CreateJavaVM

作用:创建java虚拟机
位置:jdk8u-dev/hotspot/src/share/vm/prims/jni.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* 导出JNI_CreateJavaVM函数 */
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
#else /* USDT2 */
HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
(void **) vm, penv, args);
#endif /* USDT2 */

jint result = JNI_ERR;
DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);

// We're about to use Atomic::xchg for synchronization. Some Zero
// platforms use the GCC builtin __sync_lock_test_and_set for this,
// but __sync_lock_test_and_set is not guaranteed to do what we want
// on all architectures. So we check it works before relying on it.
#if defined(ZERO) && defined(ASSERT)
{
jint a = 0xcafebabe;
jint b = Atomic::xchg(0xdeadbeef, &a);
void *c = &a;
void *d = Atomic::xchg_ptr(&b, &c);
assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
}
#endif // ZERO && ASSERT

// At the moment it's only possible to have one Java VM,
// since some of the runtime state is in global variables.

// We cannot use our mutex locks here, since they only work on
// Threads. We do an atomic compare and exchange to ensure only
// one thread can call this method at a time

// We use Atomic::xchg rather than Atomic::add/dec since on some platforms
// the add/dec implementations are dependent on whether we are running
// on a multiprocessor, and at this stage of initialization the os::is_MP
// function used to determine this will always return false. Atomic::xchg
// does not have this problem.
if (Atomic::xchg(1, &vm_created) == 1) {
return JNI_EEXIST; // already created, or create attempt in progress
}
if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
return JNI_ERR; // someone tried and failed and retry not allowed.
}

assert(vm_created == 1, "vm_created is true during the creation");

/**
* 初始化期间的某些错误是可恢复的,并且不会阻止稍后再次调用此方法(可能使用不同的参数)。
* 但是,在初始化期间的某个时刻如果发生错误,我们不能再次调用此函数(否则它将崩溃)。
* 在这些情况下,ccan_try_again标志设置为false,它将 safe_to_recreate_vm 原子地设置为1,
* 这样任何对JNI_CreateJavaVM的新调用都将立即使用上述逻辑失败。
*/
bool can_try_again = true;

/* 创建java虚拟机 */
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == JNI_OK) {
JavaThread *thread = JavaThread::current();
/* thread is thread_in_vm here */
/* 获取vm对象,从全局的main_vm赋值 */
*vm = (JavaVM *)(&main_vm);
/* 获取线程jni环境指针 */
*(JNIEnv**) penv = thread->jni_environment();

// Tracks the time application was running before GC
// 记录程序启动时间,跟从应用程序在gc前的运行时间
RuntimeService::record_application_start();

// Notify JVMTI
//
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();
}

#ifndef PRODUCT
#ifndef TARGET_OS_FAMILY_windows
#define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
#endif

// Check if we should compile all classes on bootclasspath
if (CompileTheWorld) ClassLoader::compile_the_world();
if (ReplayCompiles) ciReplay::replay(thread);

// Some platforms (like Win*) need a wrapper around these test
// functions in order to properly handle error conditions.
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif

// Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
} else {
/* 创建失败,如果允许重试,则修改safe_to_recreate_vm标志 */
if (can_try_again) {
// reset safe_to_recreate_vm to 1 so that retrial would be possible
safe_to_recreate_vm = 1;
}

// Creation failed. We must reset vm_created
// 创建失败,需要重新设置vm_create值
*vm = 0;
*(JNIEnv**)penv = 0;
// reset vm_created last to avoid race condition. Use OrderAccess to
// control both compiler and architectural-based reordering.
OrderAccess::release_store(&vm_created, 0);
}

return result;
}

Threads::create_vm

作用:hotspot创建java虚拟机函数
位置:jdk8u-dev/hotspot/src/share/vm/runtime/thread.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
//调用JDK_Version_init();
//加载libjava.so、libverify.so库,通过调用库中导出的JDK_GetVersionInfo0查询当前虚拟机版本
extern void JDK_Version_init();

// 检查当前版本是否在支持范围内,主要不支持1.1版本 高于1.1都返回true
if (!is_supported_jni_version(args->version)) return JNI_EVERSION;

//初始化系统输出流模块
ostream_init();

// 处理java启动属性.
Arguments::process_sun_java_launcher_properties(args);

// Initialize the os module before using TLS
// 初始化系统环境,例如:获取当前的进程pid、获取系统时钟、设置内存页大小
// 获取cpu数、获取物理内存大小
os::init();

// Initialize system properties.
// 设置系统属性, key-value
// 设置了java虚拟机信息、清空由os::init_system_properties_values()方法设置的值
Arguments::init_system_properties();

// So that JDK version can be used as a discrimintor when parsing arguments
// 获取jdk版本号,作为接下来参数解析的依据
JDK_Version_init();

// Update/Initialize System properties after JDK version number is known
// 设定jdk版本后再去更新系统版本、供应商属性值,此处主要设定3个参数
// jdk1.7以后的版本厂商修改为oracle,之前为sun
// 1、java.vm.specification.vendor 可选值为:Oracle Corporation / Sun Microsystems Inc.
// 2、java.vm.specification.version 可选值为:1.0 / 1.7 1.8 1.9 etc
// 3、java.vm.vendor 可选值为: Oracle Corporation / Sun Microsystems Inc.
Arguments::init_version_specific_system_properties();

// Parse arguments
// 解析参数,生成java虚拟机运行期间的一些参数指标
// -XX:Flags= -XX:+PrintVMOptions -XX:-PrintVMOptions -XX:+IgnoreUnrecognizedVMOptions
// -XX:-IgnoreUnrecognizedVMOptions -XX:+PrintFlagsInitial -XX:NativeMemoryTracking
jint parse_result = Arguments::parse(args);
if (parse_result != JNI_OK) return parse_result;

//主要完成大页支持以及linux上的coredump_filter配置
os::init_before_ergo();

//调整运行环境得参数及一些指标
//1、设置参数及指标 如果是server模式并且没有指定gc策略,则默认使用UsePaallelGC
// 64位下启用普通对象压缩
//2、设置是否使用共享存储空间(开启对象压缩、指针压缩后关闭)
//3、检查gc一致性
//4、依据物理内存设置对内存大小
//5、设置各种gc的参数标志:parallel、cms、g1、panew
//6、初始化jvm的平衡因子参数
//7、设定字节码重写标志
//8、如果指定-XX:+AggressiveOpts参数,则设定加快编译,依赖于EliminateAutoBox及DoEscapeAnalysis标志位及C2下
//9、根据是否指定gama lanucher及是否是调试状态设定暂停状态位
jint ergo_result = Arguments::apply_ergo();
if (ergo_result != JNI_OK) return ergo_result;

//暂停
if (PauseAtStartup) {
os::pause();
}

#ifndef USDT2
HS_DTRACE_PROBE(hotspot, vm__init__begin);
#else /* USDT2 */
HOTSPOT_VM_INIT_BEGIN();
#endif /* USDT2 */

// Record VM creation timing statistics
//记录jvm启动开始时间
TraceVmCreationTime create_vm_timer;
create_vm_timer.start();

// Timing (must come after argument parsing)
TraceTime timer("Create VM", TraceStartupTime);

// Initialize the os module after parsing the args
//解析参数后,初始化系统模块
//1、如果使用linux下的posix线程库cpu时钟的话,使用pthread_getcpuclockid方法获取jvm线程
// 的cpu时钟id,然后基于这个线程的时钟进行时间统计计数
//2、分配一个linux内存单页并标记位为可循环读的安全入口点
//3、初始化暂停/恢复运行的支持,主要通过注册信号处理程序支持该功能
//4、注册信号处理程序
//5、安装信号处理程序
//6、计算线程栈大小
//7、设置glibc、linux线程版本信息
//8、设置linux系统进程文件描述符值
//9、创建用于线程创建的线程锁
//11、初始化线程的优先级
jint os_init_2_result = os::init_2();
if (os_init_2_result != JNI_OK) return os_init_2_result;

//调整参数,主要针对numa架构下ParallelGC、OarallelOldGc调整堆内存参数
jint adjust_after_os_result = Arguments::adjust_after_os();
if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;

// intialize TLS
// 初始化线程本地存储,通过ptread_create_key创建一个线程特有的key_t,
// 后面通过pthread_setspecific\pthread_getspecific设置、获取线程私有数据
ThreadLocalStorage::init();

// Bootstrap native memory tracking, so it can start recording memory
// activities before worker thread is started. This is the first phase
// of bootstrapping, VM is currently running in single-thread mode.
// 启动本地内存跟踪,因此它可以在工作线程启动之前开始记录内存活动。
// 这是引导的第一阶段,JVM当前以单线程模式运行。
MemTracker::bootstrap_single_thread();

// Initialize output stream logging
// 初始化jvm的日志输出流,如果指定-Xloggc:logfilepath则根据参数指定生成输出流到文件
ostream_init_log();

// Convert -Xrun to -agentlib: if there is no JVM_OnLoad
// Must be before create_vm_init_agents()
// 如果指定了-Xrun参数,如:hprof性能分析、jdwp远程调试
if (Arguments::init_libraries_at_startup()) {
convert_vm_init_libraries_to_agents();
}

// Launch -agentlib/-agentpath and converted -Xrun agents
if (Arguments::init_agents_at_startup()) {
create_vm_init_agents();
}

// Initialize Threads state
_thread_list = NULL;
_number_of_threads = 0;
_number_of_non_daemon_threads = 0;

// Initialize global data structures and create system classes in heap
// 1、初始化全局结构体
// 2、在内存中创建jvm体系的class文件
// 3、初始化事件记录日志对象
// 4、初始化全局资源锁mutex
// 5、内存池初始化large_pool、medium_pool、small_poll、tiny_pool
// 6、启用该perfdata功能。此选项默认启用以允许JVM监视和性能测试,如果指定 -XX:+UsePerfData
vm_init_globals();

// Attach the main thread to this os thread
// 创建新的主java线程, 设置主线程状态为运行在jvm里面
JavaThread* main_thread = new JavaThread();
main_thread->set_thread_state(_thread_in_vm);
// must do this before set_active_handles and initialize_thread_local_storage
// Note: on solaris initialize_thread_local_storage() will (indirectly)
// change the stack size recorded here to one based on the java thread
// stacksize. This adjusted size is what is used to figure the placement
// of the guard pages.
// 记录栈基址、栈大小值
main_thread->record_stack_base_and_size();
// 初始化主线程的本地存储
main_thread->initialize_thread_local_storage();

// 绑定jniHandleBlock指针, 处理jni逻辑句柄
main_thread->set_active_handles(JNIHandleBlock::allocate_block());

// 设定main_thread为主线程
if (!main_thread->set_as_starting_thread()) {
vm_shutdown_during_initialization(
"Failed necessary internal allocation. Out of swap space");
delete main_thread;
*canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
return JNI_ENOMEM;
}

// 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();

// Initialize Java-Level synchronization subsystem
ObjectMonitor::Initialize() ;

// Second phase of bootstrapping, VM is about entering multi-thread mode
MemTracker::bootstrap_multi_thread();

// Initialize global modules
// 初始化全局模块
//
jint status = init_globals();
if (status != JNI_OK) {
delete main_thread;
*canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
return status;
}

// Should be done after the heap is fully created
main_thread->cache_global_variables();

HandleMark hm;

{ MutexLocker mu(Threads_lock);
Threads::add(main_thread);
}

// Any JVMTI raw monitors entered in onload will transition into
// real raw monitor. VM is setup enough here for raw monitor enter.
JvmtiExport::transition_pending_onload_raw_monitors();

// Fully start NMT
MemTracker::start();

// Create the VMThread
{ TraceTime timer("Start VMThread", TraceStartupTime);
VMThread::create();
Thread* vmthread = VMThread::vm_thread();

if (!os::create_thread(vmthread, os::vm_thread))
vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

// Wait for the VM thread to become ready, and VMThread::run to initialize
// Monitors can have spurious returns, must always check another state flag
{
MutexLocker ml(Notify_lock);
os::start_thread(vmthread);
while (vmthread->active_handles() == NULL) {
Notify_lock->wait();
}
}
}

assert (Universe::is_fully_initialized(), "not initialized");
if (VerifyDuringStartup) {
// Make sure we're starting with a clean slate.
VM_Verify verify_op;
VMThread::execute(&verify_op);
}

EXCEPTION_MARK;

// At this point, the Universe is initialized, but we have not executed
// any byte code. Now is a good time (the only time) to dump out the
// internal state of the JVM for sharing.
if (DumpSharedSpaces) {
MetaspaceShared::preload_and_dump(CHECK_0);
ShouldNotReachHere();
}

// Always call even when there are not JVMTI environments yet, since environments
// may be attached late and JVMTI must track phases of VM execution
JvmtiExport::enter_start_phase();

// Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
JvmtiExport::post_vm_start();

{
TraceTime timer("Initialize java.lang classes", TraceStartupTime);

if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}

initialize_class(vmSymbols::java_lang_String(), CHECK_0);

// Initialize java_lang.System (needed before creating the thread)
initialize_class(vmSymbols::java_lang_System(), CHECK_0);
initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
Handle thread_group = create_initial_thread_group(CHECK_0);
Universe::set_main_thread_group(thread_group());
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
// Set thread status to running since main thread has
// been started and running.
java_lang_Thread::set_thread_status(thread_object,
java_lang_Thread::RUNNABLE);

// The VM creates & returns objects of this class. Make sure it's initialized.
initialize_class(vmSymbols::java_lang_Class(), CHECK_0);

// The VM preresolves methods to these classes. Make sure that they get initialized
initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0);
call_initializeSystemClass(CHECK_0);

// get the Java runtime name after java.lang.System is initialized
JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));

// an instance of OutOfMemory exception has been allocated earlier
initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
}

// See : bugid 4211085.
// Background : the static initializer of java.lang.Compiler tries to read
// property"java.compiler" and read & write property "java.vm.info".
// When a security manager is installed through the command line
// option "-Djava.security.manager", the above properties are not
// readable and the static initializer for java.lang.Compiler fails
// resulting in a NoClassDefFoundError. This can happen in any
// user code which calls methods in java.lang.Compiler.
// Hack : the hack is to pre-load and initialize this class, so that only
// system domains are on the stack when the properties are read.
// Currently even the AWT code has calls to methods in java.lang.Compiler.
// On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
// Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
// read and write"java.vm.info" in the default policy file. See bugid 4211383
// Once that is done, we should remove this hack.
initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);

// More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
// the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
// compiler does not get loaded through java.lang.Compiler). "java -version" with the
// hotspot vm says "nojit" all the time which is confusing. So, we reset it here.
// This should also be taken out as soon as 4211383 gets fixed.
reset_vm_info_property(CHECK_0);

quicken_jni_functions();

// Must be run after init_ft which initializes ft_enabled
if (TRACE_INITIALIZE() != JNI_OK) {
vm_exit_during_initialization("Failed to initialize tracing backend");
}

// Set flag that basic initialization has completed. Used by exceptions and various
// debug stuff, that does not work until all basic classes have been initialized.
set_init_completed();

#ifndef USDT2
HS_DTRACE_PROBE(hotspot, vm__init__end);
#else /* USDT2 */
HOTSPOT_VM_INIT_END();
#endif /* USDT2 */

// record VM initialization completion time
#if INCLUDE_MANAGEMENT
Management::record_vm_init_completed();
#endif // INCLUDE_MANAGEMENT

// Compute system loader. Note that this has to occur after set_init_completed, since
// valid exceptions may be thrown in the process.
// Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
// set_init_completed has just been called, causing exceptions not to be shortcut
// anymore. We call vm_exit_during_initialization directly instead.
SystemDictionary::compute_java_system_loader(THREAD);
if (HAS_PENDING_EXCEPTION) {
vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
}

#if INCLUDE_ALL_GCS
// Support for ConcurrentMarkSweep. This should be cleaned up
// and better encapsulated. The ugly nested if test would go away
// once things are properly refactored. XXX YSR
if (UseConcMarkSweepGC || UseG1GC) {
if (UseConcMarkSweepGC) {
ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
} else {
ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
}
if (HAS_PENDING_EXCEPTION) {
vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
}
}
#endif // INCLUDE_ALL_GCS

// Always call even when there are not JVMTI environments yet, since environments
// may be attached late and JVMTI must track phases of VM execution
JvmtiExport::enter_live_phase();

// Signal Dispatcher needs to be started before VMInit event is posted
os::signal_init();

// Start Attach Listener if +StartAttachListener or it can't be started lazily
if (!DisableAttachMechanism) {
AttachListener::vm_start();
if (StartAttachListener || AttachListener::init_at_startup()) {
AttachListener::init();
}
}

// Launch -Xrun agents
// Must be done in the JVMTI live phase so that for backward compatibility the JDWP
// back-end can launch with -Xdebug -Xrunjdwp.
if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}

// Notify JVMTI agents that VM initialization is complete - nop if no agents.
JvmtiExport::post_vm_initialized();

if (TRACE_START() != JNI_OK) {
vm_exit_during_initialization("Failed to start tracing backend.");
}

if (CleanChunkPoolAsync) {
Chunk::start_chunk_pool_cleaner_task();
}

// initialize compiler(s)
#if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
CompileBroker::compilation_init();
#endif

if (EnableInvokeDynamic) {
// Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
// It is done after compilers are initialized, because otherwise compilations of
// signature polymorphic MH intrinsics can be missed
// (see SystemDictionary::find_method_handle_intrinsic).
initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
}

#if INCLUDE_MANAGEMENT
Management::initialize(THREAD);
#endif // INCLUDE_MANAGEMENT

if (HAS_PENDING_EXCEPTION) {
// management agent fails to start possibly due to
// configuration problem and is responsible for printing
// stack trace if appropriate. Simply exit VM.
vm_exit(1);
}

if (Arguments::has_profile()) FlatProfiler::engage(main_thread, true);
if (MemProfiling) MemProfiler::engage();
StatSampler::engage();
if (CheckJNICalls) JniPeriodicChecker::engage();

BiasedLocking::init();

if (JDK_Version::current().post_vm_init_hook_enabled()) {
call_postVMInitHook(THREAD);
// The Java side of PostVMInitHook.run must deal with all
// exceptions and provide means of diagnosis.
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION;
}
}

{
MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
// Make sure the watcher thread can be started by WatcherThread::start()
// or by dynamic enrollment.
WatcherThread::make_startable();
// Start up the WatcherThread if there are any periodic tasks
// NOTE: All PeriodicTasks should be registered by now. If they
// aren't, late joiners might appear to start slowly (we might
// take a while to process their first tick).
if (PeriodicTask::num_tasks() > 0) {
WatcherThread::start();
}
}

// Give os specific code one last chance to start
os::init_3();

create_vm_timer.end();
#ifdef ASSERT
_vm_complete = true;
#endif
return JNI_OK;
}
文章目录
  1. 1. java启动
    1. 1.1. main
    2. 1.2. JLI_Launch
    3. 1.3. JVMInit
    4. 1.4. ContinueInNewThread
    5. 1.5. ContinueInNewThread0
    6. 1.6. JavaMain
    7. 1.7. InitializeJVM
    8. 1.8. JNI_CreateJavaVM
    9. 1.9. Threads::create_vm