从前文《openjdk1.8工程结构》我知道makefile如何编译出我的java命令,同时也告知了程序入口main.c
main.c
作用:程序入口
位置:openjdk\jdk\src\share\bin\main.c
/*
* 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。
JLI_Launch
作用:java入口函数,解析参数、创建环境、加载jvm动态库
位置:jdk8u-dev/jdk/src/share/bin/java.c
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);
}
//选择jre的版本,这个函数实现的功能比较简单,就是选择正确的jre版本来作为即将运行java程序的版本。
//选择的方式
//1.环境变量设置了_JAVA_VERSION_SET,那么代表已经选择了jre的版本,不再进行选择
//2.可以从jar包中读取META-INF/MAINFEST.MF中获取,查看mian_class
//3.运行时给定的参数来搜索不同的目录选择jre_restrict_search
//最终会解析出一个真正需要的jre版本并且判断当前执行本java程序的jre版本是不是和这个版本一样,如果不一样调用linux的execv函数终止当前进出并且使用新的jre版本重新运行这个java程序,但是进程ID不会改变。
SelectVersion(argc, argv, &main_class);
//确定一下jvm的信息并且初始化相关信息,为后面的jvm执行准备环境
//1.首先查找jre路径,通过GetApplicationHome来获得
//2.推断JAVA_DLL文件位置,JVM.cfg文件
//3.根据JVM.cfg获取JVM_DLL位置,可以通过-XXaltJVM或者JDK_ALTERNATE_VM指定JVM_DLL
CreateExecutionEnvironment(&argc,
&argv,
jrepath,
sizeof(jrepath),
jvmpath,
sizeof(jvmpath),
jvmcfg,
sizeof(jvmcfg));
if (!IsJavaArgs()) {
//处理-XX:NativeMemoryTracking
//Hotspot VM用来分析VM内部内存使用情况的一个功能
SetJvmEnvironment(argc,argv);
}
//初始设置函数指针为无效指针
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
//如果时debug的话,记录启动时间
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
//动态加载JVM_DLL这个共享库,把hotspot的接口爆露出来,例如JNI_CreateJavaVM函数
//填充到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的虚拟机并调用java运行入口函数
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
- SelectVersion
选择正确的jre版本来作为即将运行java程序的版本
适用于一台机子上要执行多个不同版本的java项目- CreateExecutionEnvironment
为后面的jvm执行准备环境
找到了JAVA_DLL,JVM_DLL- LoadJavaVM
动态加载JVM_DLL这个共享库,从中获取JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs和JNI_GetCreatedJavaVMs三个函数的实现,其中JNI_CreateJavaVM是JVM初始化的核心入口,具体实现在hotspot目录中- ParseArguments
解析命令行参数,如-version,-help等参数在该方法中解析的- SetJavaCommandLineProp
解析形如-Dsun.java.command=的命令行参数- SetJavaLauncherPlatformProps
解析形如-Dsun.java.launcher.*的命令行参数- JVMInit
进一步调用ContinueInNewThread
ContinueInNewThread
作用:组织参数,执行真正的虚拟机入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c
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;
}
}
如果没设置threadStackSize,从GetDefaultJavaVMInitArgs获取并设置,
规整参数调用ContinueInNewThread0
ContinueInNewThread0
作用:尝试创建新线程执行代码逻辑,创建新线程失败则在当前线程执行代码逻辑
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c
/*
* 暂停当前线程,然后继续执行一个新的线程
*/
int
ContinueInNewThread0(
int (JNICALL *continuation)(void *),/线程入口函数,在这里具体执行的时JavaMain方法
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);//可join线程
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);//线程回收
return rslt;
}
创建一线程执行JavaMain函数
JavaMain
作用:虚拟机的入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c
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();
//通过CreateJavaVM导出jvm到&vm, &env
//&vm主要提供虚拟整体的操作
//&env主要提供虚拟启动的环境的操作
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();
}
}
/* 如果没有指定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 */
//从环境中取出java主类
mainClass = LoadMainClass(env, mode, what);
/* 检查是否指定main class, 不存在则退出虚拟机*/
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/* 获取应用的class文件 */
//JavaFX gui应用相关可忽略
appClass = GetApplicationClass(env);
/* 检查是否指定app class, 不存在返回-1 */
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*window和类unix为空实现,在macos下设定gui程序的程序名称*/
//JavaFX gui应用相关可忽略
PostJVMInit(env, appClass, vm);
/*从mainclass中加载静态main方法*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
//组装main函数参数
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;
//main方法执行完毕,JVM退出,包含两步:
//(*vm)->DetachCurrentThread,让当前Main线程同启动线程断联
//创建一个新的名为DestroyJavaVM的线程,让该线程等待所有的非后台进程退出,并在最后执行(*vm)->DestroyJavaVM方法。
LEAVE();
}
- InitializeJVM
初始化JVM,通过调用JNI_CreateJavaVM给JavaVM和JNIEnv对象正确赋值- LoadMainClass
获取应用程序的MainClass,即包含java程序启动入口main方法的类,- GetApplicationClass
JavaFX没有MainClass而是通过ApplicationClass启动的,这里获取ApplicationClass- PostJVMInit 将ApplicationClass作为应用名传给JavaFX本身,比如作为主菜单
- (*env)->GetStaticMethodID
获取main方法的方法ID- CreateApplicationArgs
解析main方法的参数- (*env)->CallStaticVoidMethod 执行main方法
- LEAVE
main方法执行完毕,JVM退出
JNI_CreateJavaVM
作用:创建java虚拟机
位置:jdk8u-dev/hotspot/src/share/vm/prims/jni.cpp
/* 导出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();
// 通知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
//根据配置加载类路径中所有的类
if (CompileTheWorld) ClassLoader::compile_the_world();
if (ReplayCompiles) ciReplay::replay(thread);
// win* 系统添加异常处理的wrapper
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif
//设置当前线程的线程状态
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;
}
// 创建失败,需要重新设置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创建虚拟机,并设置vm,env返回
Threads::create_vm
作用:hotspot创建java虚拟机函数
位置:jdk8u-dev/hotspot/src/share/vm/runtime/thread.cpp
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());
//装载Thread class对象
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
//创建thread实例
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;
}
此方法真正创建的jvm 其中JNIEnv对象的初始化在 JavaThread* main_thread = new
JavaThread()时, JavaThread()这里调用JavaThread(bool is_attaching_via_jni =
false)的构造方法,
然后执行initialize()-》set_jni_functions(jni_functions());完成初始化,
jni_functions()方法返回thread.cpp中的一个全局变量
主要参考
《hotspot实战》
《OpenJdk1.8笔记–java启动流程》