(轉(zhuǎn)載)深入理解Java之jvm啟動(dòng)流程
用戶1148531發(fā)表于向治洪
地址:https://cloud.tencent.com/developer/article/1038435
在這篇文章中:
概述
jvm啟動(dòng)流程分析
配置JVM裝載環(huán)境
虛擬機(jī)參數(shù)解析
設(shè)置線程棧大小
執(zhí)行main方法
概述
當(dāng)我們寫一個(gè)Java類,并重寫Main方法,程序就能運(yùn)行起來(lái)。main方法的背后,程序?yàn)槭裁茨苓\(yùn)行,jvm究竟做了什么處理?要理解這些,就需要了解jvm的設(shè)計(jì)原理以及啟動(dòng)的流程。
虛擬機(jī)的啟動(dòng)入口位于share/tools/launcher/java.c的main方法,整個(gè)流程分為如下幾個(gè)步驟: 1、配置JVM裝載環(huán)境 2、解析虛擬機(jī)參數(shù) 3、設(shè)置線程棧大小 4、執(zhí)行Java main方法
jvm啟動(dòng)流程分析
配置JVM裝載環(huán)境
Java代碼執(zhí)行時(shí)需要一個(gè)JVM環(huán)境,JVM環(huán)境的創(chuàng)建包括兩部分:JVM.dll文件的查找和裝載。
JVM.dll文件查找
我們來(lái)看一段Jre通過(guò)環(huán)境的路徑和系統(tǒng)版本尋找jvm.cfg文件的代碼。

說(shuō)明: 1、GetJREPath()查找當(dāng)前JRE環(huán)境的所在路徑; 2、ReadKnownVms()讀取JRE路徑\lib\ARCH(CPU構(gòu)架)\JVM.cfg文件,其中ARCH(CPU構(gòu)架)通過(guò)GetArch方法獲取,在window下有三種情況:amd64、ia64和i386; 3、CheckJvmType確定當(dāng)前JVM類型,先判斷是否通過(guò)-J、-XXaltjvm=或-J-XXaltjvm=參數(shù)指定,如果沒(méi)有,則讀取JVM.cfg文件中配置的第一個(gè)類型; 4、GetJVMPath根據(jù)上一步確定的JVM類型,找到對(duì)應(yīng)的JVM.dll文件;
JVM.dll文件的裝載
調(diào)用JVM.dll文件中定義的函數(shù)初始化虛擬機(jī)中的函數(shù)。

JVM.dll文件的裝載:
1、LoadLibrary方法裝載JVM.dll動(dòng)態(tài)連接庫(kù); 2、把JVM.dll文件中定義的函數(shù)JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs綁定到InvocationFunctions變量的CreateJavaVM和GetDefaultJavaVMInitArgs函數(shù)指針變量上;
虛擬機(jī)參數(shù)解析
裝載完JVM環(huán)境之后,需要對(duì)啟動(dòng)參數(shù)進(jìn)行解析,其實(shí)在裝載JVM環(huán)境的過(guò)程中已經(jīng)解析了部分參數(shù),該過(guò)程通過(guò)ParseArguments方法實(shí)現(xiàn),并調(diào)用AddOption方法將解析完成的參數(shù)保存到JavaVMOption中,JavaVMOption結(jié)構(gòu)實(shí)現(xiàn)如下:

AddOption代碼:

AddOption核心就是對(duì)-Xss參數(shù)進(jìn)行特殊處理,并設(shè)置threadStackSize,因?yàn)閰?shù)格式比較特殊,其它是key/value鍵值對(duì),它是-Xss512的格式。后續(xù)Arguments類會(huì)對(duì)JavaVMOption數(shù)據(jù)進(jìn)行再次處理,并驗(yàn)證參數(shù)的合理性。
參數(shù)處理
Arguments::parse_each_vm_init_arg方法負(fù)責(zé)處理經(jīng)過(guò)解析過(guò)的JavaVMOption數(shù)據(jù),部分實(shí)現(xiàn)如下:

這里列出了JavaVMOption三個(gè)常用的參數(shù): 1、-Xmn:設(shè)置新生代的大小NewSize和MaxNewSize; 2、-Xms:設(shè)置堆的初始值InitialHeapSize,也是堆的最小值; 3、-Xmx:設(shè)置堆的最大值MaxHeapSize;
參數(shù)驗(yàn)證
Arguments::check_gc_consistency方法負(fù)責(zé)驗(yàn)證虛擬機(jī)啟動(dòng)參數(shù)中配置GC的合理性,實(shí)現(xiàn)如下:

1、如果參數(shù)為-XX:+UseSerialGC -XX:+UseParallelGC,由于UseSerialGC和UseParallelGC不能兼容,JVM啟動(dòng)時(shí)會(huì)拋出錯(cuò)誤信息; 2、如果參數(shù)為-XX:+UseConcMarkSweepGC -XX:+UseParNewGC,其中UseConcMarkSweepGC和UseParNewGC可以兼容,JVM可以正常啟動(dòng);
設(shè)置線程棧大小

如果啟動(dòng)參數(shù)未設(shè)置-Xss,即threadStackSize為0,則調(diào)用InvocationFunctions的GetDefaultJavaVMInitArgs方法獲取JavaVM的初始化參數(shù),即調(diào)用JVM.dll函數(shù)JNI_GetDefaultJavaVMInitArgs,定義在share\vm\prims\jni.cpp,實(shí)現(xiàn)如下:

ThreadStackSize定義在globals.hpp中,根據(jù)當(dāng)前系統(tǒng)類型,加載對(duì)應(yīng)的配置文件,所以在不同的系統(tǒng)中,ThreadStackSize的默認(rèn)值也不同。
執(zhí)行main方法

線程棧大小確定后,通過(guò)ContinueInNewThread方法創(chuàng)建新線程,并執(zhí)行JavaMain函數(shù),JavaMain函數(shù)的大概流程如下:
1、新建JVM實(shí)例
InitializeJVM方法調(diào)用InvocationFunctions的CreateJavaVM方法,即調(diào)用JVM.dll函數(shù)JNI_CreateJavaVM,新建一個(gè)JVM實(shí)例,該過(guò)程比較復(fù)雜,會(huì)在后續(xù)文章進(jìn)行分析。
2、加載主類的class
Java運(yùn)行方式有兩種:jar方式和class方式。
jar方式:

1、調(diào)用GetMainClassName方法找到META-INF/MANIFEST.MF文件指定的Main-Class的主類名; 2、調(diào)用LoadClass方法加載主類的class文件;
class方式:

1、調(diào)用NewPlatformString方法創(chuàng)建類名的String對(duì)象; 2、調(diào)用LoadClass方法加載主類的class文件;
3,查找main方法
通過(guò)GetStaticMethodID方法查找指定方法名的靜態(tài)方法,實(shí)現(xiàn)如下:

最終調(diào)用JVM.dll函數(shù)jni_GetStaticMethodID實(shí)現(xiàn)。

其中g(shù)et_method_id方法根據(jù)類文件對(duì)應(yīng)的instanceKlass對(duì)象查找指定方法。
4、執(zhí)行main方法

1、重新創(chuàng)建參數(shù)數(shù)組; 2、其中mainID是main方法的入口地址,CallStaticVoidMethod方法最終調(diào)用JVM.dll中的jni_CallStaticVoidMethodV函數(shù)。


最終通過(guò)JavaCalls::call執(zhí)行main方法,到此Jvm調(diào)用main方法啟動(dòng)類的完整流程就講完了。