Java虛擬機啟動過程探索

一、序言

當我們在編寫Java應用的時候,很少會註意Java程序是如何被運行的,如何被操作系統管理和調度的。帶著好奇心,探索一下Java虛擬機啟動過程。

1、素材準備

Java源代碼Java字節碼Java虛擬機操作系統四個角度分解啟動過程。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HelloWorld!");
    }
}

2、源代碼生成字節碼

利用Java環境提供的可執行命令javac將源代碼編譯成字節碼文件,編譯後的字節碼文件與平臺無關,可跨平臺運行。註意區分javac命令是一個獨立的編譯應用,源代碼編譯完成,進程終止。java命令啟動的虛擬機進程的編譯過程是將字節碼指令編譯成匯編指令(二進制指令)。

3、虛擬機解析字節碼

Java字節碼無法直接在操作系統上創建進程,因此需要借助已經啟動的虛擬機進程來解析字節碼,處理字節碼有兩種常見方式:解釋型編譯型

在命令行中每運行java命令代表啟動一個Java虛擬機進程,各虛擬機相互獨立,通過命令行參數分別對虛擬機進程進行配置。

Java虛擬機準備啟動完畢後,便可以依次解析字節碼指令,正式運行Java代碼部分。

4、操作系統管理虛擬機

操作系統通過進程管理和調度Java虛擬機,無法感知虛擬機間接解析Java字節碼部分。Java字節碼通過虛擬機的抽象,完成瞭在操作系統上運行。

二、Java虛擬機

當運行Java應用時,需要先安裝Java環境,然而安裝的Java環境與Java應用有什麼關系,Java應用是如何運行起來的,下面一探究竟。

二進制可執行程序${JAVA_HOME}/bin/java是C++編寫經過GCC編譯器編譯後形成的,探索Java虛擬機的運行原理,首先需要找到相應的源碼。

當在安裝Java環境時,會看到一個src.zip壓縮文件,解壓後裡面launcher/java.c文件便是可執行文件java命令的主要源碼。

虛擬機的啟動入口位於launcher/java.cmain方法,整個流程分為如下幾個步驟: 配置JVM裝載環境;解析虛擬機參數;設置線程棧大小;執行Java main方法

(一)配置JVM裝載環境

從操作系統加載環境變量、硬件信息等運行環境信息,為後續創建JVM進程做準備。

(二)命令行參數解析

裝載完JVM環境之後,需要對啟動時命令行參數進行解析,該過程通過ParseArguments方法實現,並調用AddOption方法將解析完成的參數保存到JavaVMOption中。

比如常見的JavaVMOption參數在此步驟解析:

-Xms:設置堆的初始值InitialHeapSize,也是堆的最小值; 
-Xmx:設置堆的最大值MaxHeapSize;

JVM調優各參數解析便是在此步驟完成的。

(三)執行main方法

線程棧大小確定後,通過ContinueInNewThread方法創建新線程,並執行JavaMain函數,大概流程如下:

1、新建JVM實例

InitializeJVM方法調用InvocationFunctions的CreateJavaVM方法,即調用JVM.dll函數JNI_CreateJavaVM,新建一個JVM實例,該過程比較復雜。

2、加載入口類

通常在命令行中運行如下命令即指明入口類路徑

# 直接指名入口類路徑
java HelloWorld.class
# 通過包類配置入口類路徑
java -jar HelloWorld.jar

3、查找main方法

通過GetStaticMethodID方法查找指定main方法名的靜態方法。

4、執行main方法

通過JavaCalls::call回調執行main方法。需要註意的是,這裡執行main方法不是Java語言的方法,是經過虛擬機解釋(或者編譯)後,操作系統能夠理解的二進制可執行方法。

三、解析字節碼

(一)解釋字節碼

1、基於棧指令集

iconst_1    將 1 放入棧頂
iconst_1    將 1 放入棧頂
iadd        將棧頂的 2 個數相加後結果放入棧頂
istore_0    將相加的結果放入局部變量表

基於棧的指令集優點是虛擬機解釋器是可跨平臺移植的,換句話說不同平臺的虛擬機解釋器代碼可以復用。

2、基於寄存器指令集

mov eax,1 把 EAX 寄存器的值設為 1
add eax,1 再把這個值加 1 ,結果保存在瞭 EAX 寄存器

基於寄存器指令集的優點是執行速度相對於棧較快,原因是出棧入棧本身就涉及瞭大量的指令,而且棧是在內存中實現的,更底層的匯編指令性能更高。

基於寄存器指令集的缺點是虛擬機解釋器是不可跨平臺移植,需要針對不同平臺的虛擬機做不同實現。考慮到不同平臺已經使用不同的虛擬機程序,因此此過程多用戶透明。

虛擬機通過解釋器來翻譯字節碼文件中的指令比較順其自然,可是對於服務器端高頻執行的程序來說,中間的翻譯過程相對耗時。解釋字節碼的方式適用於對啟動性能要求高,並且執行頻率較低的應用程序。

(二)編譯字節碼

最初,JVM 中的字節碼是由解釋器( Interpreter )完成編譯的,當虛擬機發現某個方法或代碼塊的運行特別頻繁的時候,就會把這些代碼認定為熱點代碼

為瞭提高熱點代碼的執行效率,在運行時,即時編譯器(JIT,Just In Time)會把這些代碼編譯成與本地平臺相關的機器碼,並進行各層次的優化,然後保存到內存中。

在 HotSpot 虛擬機中,內置瞭兩種 JIT,分別為C1 編譯器C2 編譯器,這兩個編譯器的編譯過程是不一樣的。

1、C1 編譯器

C1 編譯器是一個簡單快速的編譯器,主要的關註點在於局部性的優化,適用於執行時間較短或對啟動性能有要求的程序,也稱為Client Compiler,例如,GUI 應用對界面啟動速度就有一定要求。

2、C2 編譯器

C2 編譯器是為長期運行的服務器端應用程序做性能調優的編譯器,適用於執行時間較長或對峰值性能有要求的程序,也稱為Server Compiler,例如,服務器上長期運行的 Java 應用對穩定運行就有一定的要求。

3、分層編譯

分層編譯將 JVM 的執行狀態分為瞭 5 個層次:

第 0 層:程序解釋執行,默認開啟性能監控功能(Profiling),如果不開啟,可觸發第二層編譯;
第 1 層:可稱為 C1 編譯,將字節碼編譯為本地代碼,進行簡單、可靠的優化,不開啟 Profiling;
第 2 層:也稱為 C1 編譯,開啟 Profiling,僅執行帶方法調用次數和循環回邊執行次數 profiling 的 C1 編譯;
第 3 層:也稱為 C1 編譯,執行所有帶 Profiling 的 C1 編譯;
第 4 層:可稱為 C2 編譯,也是將字節碼編譯為本地代碼,但是會啟用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。

通常情況下,C2 的執行效率比 C1 高出30%以上。

在 Java8 中,默認開啟分層編譯。如果隻想開啟 C2,可以關閉分層編譯(-XX:-TieredCompilation),如果隻想用 C1,可以在打開分層編譯的同時,使用參數:-XX:TieredStopAtLevel=1

通過 java -version命令行可以查看到當前虛擬機解析字節碼的方式,mixed mode表示既有解釋模式也有即是編譯模式。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

mixed mode代表是默認的混合編譯模式,除瞭這種模式外,我們還可以使用-Xint參數強制虛擬機運行於隻有解釋器的編譯模式下;也可以使用參數-Xcomp強制虛擬機運行於隻有 JIT 的編譯模式下。

僅使用解釋模式

通過命令java -Xint -version設置僅使用解釋模式,interpreted mode表示解釋模式。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, interpreted mode)

僅使用編譯模式

通過命令java -Xcomp -version設置僅使用編譯模式,compiled mode表示編譯模式。在編譯模式下,程序啟動能感覺到明顯的卡頓。

java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, compiled mode)

四、小結

通過對Java虛擬機啟動過程的解析,特別是即時編譯環節的理解,Java應用運行並不慢。當應用中熱點代碼普遍被編譯成匯編指令(二進制可執行命令)存放於內存中時,可近似達到C語言原生程序的運行速度。

隨著算力與內存成本日漸降低,通過空間復雜度置換時間復雜度的策略顯然是合理的,使用Java語言編寫需求萬千變化的應用是第一選擇:既有跨平臺、內存安全、框架生態豐富的優點,也在運行效率方面積極改善,這種折中選擇與市場反饋保持一致。

到此這篇關於Java虛擬機啟動過程解析的文章就介紹到這瞭,更多相關Java虛擬機啟動內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: