從前文《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啓動流程》