原創申明:本文由公眾號【猿燈塔】原創,轉載請說明出處標注
“365篇原創計划”第十四篇。
今天呢!燈塔君跟大家講:
JVM源碼分析之JVM啟動流程
前言:
執行Java類的main方法,程序就能運行起來,main方法的背后,虛擬機究竟發生了什么?如果你對這個感興趣,相信本文會給你一個答案,本文分析的openjdk版本為openjdk-7-fcs-src-b147-27
class BootStrap {
public static void main(String[] args) {
for (String str : args) {
System.out.println(str);
}
}
}
java BootStrap -Xms6G -Xmx8G -Xmn3G -Xss512k
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
虛擬機的啟動入口位於share/tools/launcher/java.c
的main方法,整個流程分為如下幾個步驟:
1、配置JVM裝載環境
2、解析虛擬機參數
3、設置線程棧大小
4、執行Java main方法
1、配置JVM裝載環境
Java代碼執行時需要一個JVM環境,JVM環境的創建包括兩部分:JVM.dll文件的查找和裝載。
JVM.dll文件的查找
通過CreateExecutionEnvironment
方法實現,根據當前JRE環境的路徑和系統版本尋找jvm.cfg
文件,windows實現如下:
大概實現邏輯:
1、GetJREPath
查找當前JRE環境的所在路徑;
2、ReadKnownVms
讀取JRE路徑\lib\ARCH(CPU構架)\JVM.cfg
文件,其中ARCH(CPU構架)
通過GetArch
方法獲取,在window下有三種情況:amd64、ia64和i386;3、CheckJvmType
確定當前JVM類型,先判斷是否通過-J
、-XXaltjvm=
或-J-XXaltjvm=
參數指定,如果沒有,則讀取JVM.cfg文件中配置的第一個類型;4、GetJVMPath
根據上一步確定的JVM類型,找到對應的JVM.dll文件;
JVM.dll文件的裝載
初始化虛擬機中的函數調用,即通過JVM中的方法調用JVM.dll文件中定義的函數,實現如下:
1、LoadLibrary
方法裝載JVM.dll動態連接庫;2、把JVM.dll文件中定義的函數JNI_CreateJavaVM
和JNI_GetDefaultJavaVMInitArgs
綁定到InvocationFunctions變量的CreateJavaVM
和GetDefaultJavaVMInitArgs
函數指針變量上;
2、虛擬機參數解析
裝載完JVM環境之后,需要對啟動參數進行解析,其實在裝載JVM環境的過程中已經解析了部分參數,該過程通過ParseArguments
方法實現,並調用AddOption
方法將解析完成的參數保存到JavaVMOption
中,JavaVMOption
結構實現如下:
AddOption方法實現如下:
這里對-Xss
參數進行特殊處理,並設置threadStackSize,因為參數格式比較特殊,其它是key/value鍵值對,它是-Xss512
的格式。后續Arguments
類會對JavaVMOption
數據進行再次處理,並驗證參數的合理性。
參數處理
Arguments::parse_each_vm_init_arg
方法負責處理經過解析過的JavaVMOption數據,部分實現如下:
這里只列出三個常用的參數:
1、-Xmn:設置新生代的大小NewSize和MaxNewSize;
2、-Xms:設置堆的初始值InitialHeapSize,也是堆的最小值;
3、-Xmx:設置堆的最大值MaxHeapSize;
參數驗證
Arguments::check_gc_consistency
方法負責驗證虛擬機啟動參數中配置GC的合理性,實現如下:
1、如果參數為-XX:+UseSerialGC -XX:+UseParallelGC
,由於UseSerialGC和UseParallelGC不能兼容,JVM啟動時會拋出錯誤信息;2、如果參數為-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
,其中UseConcMarkSweepGC和UseParNewGC可以兼容,JVM可以正常啟動;
3、設置線程棧大小
如果啟動參數未設置-Xss
,即threadStackSize為0,則調用InvocationFunctions的GetDefaultJavaVMInitArgs
方法獲取JavaVM的初始化參數,即調用JVM.dll函數JNI_GetDefaultJavaVMInitArgs
,定義在share\vm\prims\jni.cpp
,實現如下:
ThreadStackSize
定義在globals.hpp
中,根據當前系統類型,加載對應的配置文件,所以在不同的系統中,ThreadStackSize
的默認值也不同。
4、執行Java main方法
線程棧大小確定后,通過ContinueInNewThread
方法創建新線程,並執行JavaMain函數,JavaMain函數的大概流程如下:
1、新建JVM實例
InitializeJVM
方法調用InvocationFunctions的CreateJavaVM
方法,即調用JVM.dll函數
JNI_CreateJavaVM
,新建一個JVM實例,該過程比較復雜,會在后續文章進行分析;
2、加載主類的class
Java運行方式有兩種:jar方式和class方式。
jar方式
1、調用GetMainClassName
方法找到META-INF/MANIFEST.MF
文件指定的Main-Class的主類名;
2、調用LoadClass
方法加載主類的class文件;
class方式
1、調用NewPlatformString
方法創建類名的String對象;
2、調用LoadClass
方法加載主類的class文件;
3、查找main方法
通過GetStaticMethodID
方法查找指定方法名的靜態方法,實現如下:
最終調用JVM.dll
函數jni_GetStaticMethodID
實現
其中get_method_id
方法根據類文件對應的instanceKlass對象查找指定方法。
4、執行main方法
1、重新創建參數數組;2、其中mainID是main方法的入口地址,CallStaticVoidMethod
方法最終調用JVM.dll
中的jni_CallStaticVoidMethodV
函數,實現如下
jni_invoke_static實現如下:
最終通過JavaCalls::call
執行main方法。
365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回復【資料】【面試】【簡歷】有我准備的一線大廠面試資料和簡歷模板