Java虛擬機(jī)啟動(dòng)過程探索
一、序言
當(dāng)我們?cè)诰帉慗ava應(yīng)用的時(shí)候,很少會(huì)注意Java程序是如何被運(yùn)行的,如何被操作系統(tǒng)管理和調(diào)度的。帶著好奇心,探索一下Java虛擬機(jī)啟動(dòng)過程。
1、素材準(zhǔn)備
從Java源代碼
、Java字節(jié)碼
、Java虛擬機(jī)
、操作系統(tǒng)
四個(gè)角度分解啟動(dòng)過程。
public class HelloWorld { public static void main(String[] args) { System.out.println("HelloWorld!"); } }
2、源代碼生成字節(jié)碼
利用Java環(huán)境提供的可執(zhí)行命令javac
將源代碼編譯成字節(jié)碼文件,編譯后的字節(jié)碼文件與平臺(tái)無關(guān),可跨平臺(tái)運(yùn)行。注意區(qū)分javac
命令是一個(gè)獨(dú)立的編譯應(yīng)用,源代碼編譯完成,進(jìn)程終止。java
命令啟動(dòng)的虛擬機(jī)進(jìn)程的編譯過程是將字節(jié)碼指令編譯成匯編指令(二進(jìn)制指令)。
3、虛擬機(jī)解析字節(jié)碼
Java字節(jié)碼無法直接在操作系統(tǒng)上創(chuàng)建進(jìn)程,因此需要借助已經(jīng)啟動(dòng)的虛擬機(jī)進(jìn)程來解析字節(jié)碼,處理字節(jié)碼有兩種常見方式:解釋型
和編譯型
。
在命令行中每運(yùn)行java
命令代表啟動(dòng)一個(gè)Java虛擬機(jī)進(jìn)程,各虛擬機(jī)相互獨(dú)立,通過命令行參數(shù)分別對(duì)虛擬機(jī)進(jìn)程進(jìn)行配置。
Java虛擬機(jī)準(zhǔn)備啟動(dòng)完畢后,便可以依次解析字節(jié)碼指令,正式運(yùn)行Java代碼
部分。
4、操作系統(tǒng)管理虛擬機(jī)
操作系統(tǒng)通過進(jìn)程管理和調(diào)度Java虛擬機(jī),無法感知虛擬機(jī)間接解析Java字節(jié)碼部分。Java字節(jié)碼通過虛擬機(jī)的抽象,完成了在操作系統(tǒng)上運(yùn)行。
二、Java虛擬機(jī)
當(dāng)運(yùn)行Java應(yīng)用時(shí),需要先安裝Java環(huán)境,然而安裝的Java環(huán)境與Java應(yīng)用有什么關(guān)系,Java應(yīng)用是如何運(yùn)行起來的,下面一探究竟。
二進(jìn)制可執(zhí)行程序${JAVA_HOME}/bin/java
是C++編寫經(jīng)過GCC編譯器編譯后形成的,探索Java虛擬機(jī)的運(yùn)行原理,首先需要找到相應(yīng)的源碼。
當(dāng)在安裝Java環(huán)境時(shí),會(huì)看到一個(gè)src.zip
壓縮文件,解壓后里面launcher/java.c
文件便是可執(zhí)行文件java
命令的主要源碼。
虛擬機(jī)的啟動(dòng)入口位于launcher/java.c
的main方法
,整個(gè)流程分為如下幾個(gè)步驟: 配置JVM裝載環(huán)境;解析虛擬機(jī)參數(shù);設(shè)置線程棧大小;執(zhí)行Java main方法
(一)配置JVM裝載環(huán)境
從操作系統(tǒng)加載環(huán)境變量、硬件信息等運(yùn)行環(huán)境信息,為后續(xù)創(chuàng)建JVM進(jìn)程做準(zhǔn)備。
(二)命令行參數(shù)解析
裝載完JVM環(huán)境之后,需要對(duì)啟動(dòng)時(shí)命令行參數(shù)進(jìn)行解析,該過程通過ParseArguments方法
實(shí)現(xiàn),并調(diào)用AddOption方法
將解析完成的參數(shù)保存到JavaVMOption中。
比如常見的JavaVMOption參數(shù)在此步驟解析:
-Xms:設(shè)置堆的初始值InitialHeapSize,也是堆的最小值;
-Xmx:設(shè)置堆的最大值MaxHeapSize;
JVM調(diào)優(yōu)各參數(shù)解析便是在此步驟完成的。
(三)執(zhí)行main方法
線程棧大小確定后,通過ContinueInNewThread方法
創(chuàng)建新線程,并執(zhí)行JavaMain函數(shù),大概流程如下:
1、新建JVM實(shí)例
InitializeJVM方法調(diào)用InvocationFunctions的CreateJavaVM方法,即調(diào)用JVM.dll函數(shù)JNI_CreateJavaVM,新建一個(gè)JVM實(shí)例,該過程比較復(fù)雜。
2、加載入口類
通常在命令行中運(yùn)行如下命令即指明入口類路徑
# 直接指名入口類路徑 java HelloWorld.class # 通過包類配置入口類路徑 java -jar HelloWorld.jar
3、查找main方法
通過GetStaticMethodID方法查找指定main方法名的靜態(tài)方法。
4、執(zhí)行main方法
通過JavaCalls::call
回調(diào)執(zhí)行main方法。需要注意的是,這里執(zhí)行main方法不是Java語言的方法,是經(jīng)過虛擬機(jī)解釋(或者編譯)后,操作系統(tǒng)能夠理解的二進(jìn)制可執(zhí)行方法。
三、解析字節(jié)碼
(一)解釋字節(jié)碼
1、基于棧指令集
iconst_1 將 1 放入棧頂 iconst_1 將 1 放入棧頂 iadd 將棧頂?shù)?2 個(gè)數(shù)相加后結(jié)果放入棧頂 istore_0 將相加的結(jié)果放入局部變量表
基于棧的指令集優(yōu)點(diǎn)是虛擬機(jī)解釋器是可跨平臺(tái)移植的,換句話說不同平臺(tái)的虛擬機(jī)解釋器代碼可以復(fù)用。
2、基于寄存器指令集
mov eax,1 把 EAX 寄存器的值設(shè)為 1 add eax,1 再把這個(gè)值加 1 ,結(jié)果保存在了 EAX 寄存器
基于寄存器指令集的優(yōu)點(diǎn)是執(zhí)行速度相對(duì)于棧較快,原因是出棧入棧本身就涉及了大量的指令,而且棧是在內(nèi)存中實(shí)現(xiàn)的,更底層的匯編指令性能更高。
基于寄存器指令集的缺點(diǎn)是虛擬機(jī)解釋器是不可跨平臺(tái)移植,需要針對(duì)不同平臺(tái)的虛擬機(jī)做不同實(shí)現(xiàn)??紤]到不同平臺(tái)已經(jīng)使用不同的虛擬機(jī)程序,因此此過程多用戶透明。
虛擬機(jī)通過解釋器來翻譯字節(jié)碼文件中的指令比較順其自然,可是對(duì)于服務(wù)器端高頻執(zhí)行的程序來說,中間的翻譯過程相對(duì)耗時(shí)。解釋字節(jié)碼的方式適用于對(duì)啟動(dòng)性能要求高,并且執(zhí)行頻率較低的應(yīng)用程序。
(二)編譯字節(jié)碼
最初,JVM 中的字節(jié)碼是由解釋器( Interpreter )完成編譯的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁的時(shí)候,就會(huì)把這些代碼認(rèn)定為熱點(diǎn)代碼
。
為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時(shí),即時(shí)編譯器(JIT,Just In Time)會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各層次的優(yōu)化,然后保存到內(nèi)存中。
在 HotSpot 虛擬機(jī)中,內(nèi)置了兩種 JIT,分別為C1 編譯器
和C2 編譯器
,這兩個(gè)編譯器的編譯過程是不一樣的。
1、C1 編譯器
C1 編譯器是一個(gè)簡(jiǎn)單快速的編譯器,主要的關(guān)注點(diǎn)在于局部性的優(yōu)化,適用于執(zhí)行時(shí)間較短或?qū)?dòng)性能有要求的程序,也稱為Client Compiler
,例如,GUI 應(yīng)用對(duì)界面啟動(dòng)速度就有一定要求。
2、C2 編譯器
C2 編譯器是為長期運(yùn)行的服務(wù)器端應(yīng)用程序做性能調(diào)優(yōu)的編譯器,適用于執(zhí)行時(shí)間較長或?qū)Ψ逯敌阅苡幸蟮某绦?,也稱為Server Compiler
,例如,服務(wù)器上長期運(yùn)行的 Java 應(yīng)用對(duì)穩(wěn)定運(yùn)行就有一定的要求。
3、分層編譯
分層編譯將 JVM 的執(zhí)行狀態(tài)分為了 5 個(gè)層次:
第 0 層:程序解釋執(zhí)行,默認(rèn)開啟性能監(jiān)控功能(Profiling),如果不開啟,可觸發(fā)第二層編譯;
第 1 層:可稱為 C1 編譯,將字節(jié)碼編譯為本地代碼,進(jìn)行簡(jiǎn)單、可靠的優(yōu)化,不開啟 Profiling;
第 2 層:也稱為 C1 編譯,開啟 Profiling,僅執(zhí)行帶方法調(diào)用次數(shù)和循環(huán)回邊執(zhí)行次數(shù) profiling 的 C1 編譯;
第 3 層:也稱為 C1 編譯,執(zhí)行所有帶 Profiling 的 C1 編譯;
第 4 層:可稱為 C2 編譯,也是將字節(jié)碼編譯為本地代碼,但是會(huì)啟用一些編譯耗時(shí)較長的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化。
通常情況下,C2 的執(zhí)行效率比 C1 高出30%以上。
在 Java8 中,默認(rèn)開啟分層編譯。如果只想開啟 C2,可以關(guān)閉分層編譯(-XX:-TieredCompilation
),如果只想用 C1,可以在打開分層編譯的同時(shí),使用參數(shù):-XX:TieredStopAtLevel=1
。
通過 java -version
命令行可以查看到當(dāng)前虛擬機(jī)解析字節(jié)碼的方式,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
代表是默認(rèn)的混合編譯模式,除了這種模式外,我們還可以使用-Xint
參數(shù)強(qiáng)制虛擬機(jī)運(yùn)行于只有解釋器的編譯模式下;也可以使用參數(shù)-Xcomp
強(qiáng)制虛擬機(jī)運(yùn)行于只有 JIT 的編譯模式下。
僅使用解釋模式
通過命令java -Xint -version
設(shè)置僅使用解釋模式,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
設(shè)置僅使用編譯模式,compiled mode
表示編譯模式。在編譯模式下,程序啟動(dòng)能感覺到明顯的卡頓。
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)
四、小結(jié)
通過對(duì)Java虛擬機(jī)啟動(dòng)過程的解析,特別是即時(shí)編譯
環(huán)節(jié)的理解,Java應(yīng)用運(yùn)行并不慢。當(dāng)應(yīng)用中熱點(diǎn)代碼普遍被編譯成匯編指令(二進(jìn)制可執(zhí)行命令)存放于內(nèi)存中時(shí),可近似達(dá)到C語言原生程序的運(yùn)行速度。
隨著算力與內(nèi)存成本日漸降低,通過空間復(fù)雜度置換時(shí)間復(fù)雜度的策略顯然是合理的,使用Java語言編寫需求萬千變化的應(yīng)用是第一選擇:既有跨平臺(tái)、內(nèi)存安全、框架生態(tài)豐富的優(yōu)點(diǎn),也在運(yùn)行效率方面積極改善,這種折中選擇與市場(chǎng)反饋保持一致。
到此這篇關(guān)于Java虛擬機(jī)啟動(dòng)過程解析的文章就介紹到這了,更多相關(guān)Java虛擬機(jī)啟動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)遞歸讀取文件夾下的所有文件
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)遞歸讀取文件夾下的所有文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02java模擬發(fā)送form-data的請(qǐng)求方式
這篇文章主要介紹了java模擬發(fā)送form-data的請(qǐng)求方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-05-05一個(gè)處理用戶登陸的servlet簡(jiǎn)單實(shí)例
這篇文章主要介紹了一個(gè)處理用戶登陸的servlet簡(jiǎn)單實(shí)例,可通過servlet實(shí)現(xiàn)處理用戶登錄的功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01idea 無法創(chuàng)建Scala class 選項(xiàng)的原因分析及解決辦法匯總
這篇文章主要介紹了idea 無法創(chuàng)建Scala class 選項(xiàng)的解決辦法匯總,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09解決logback-classic 使用testCompile的打包問題
這篇文章主要介紹了解決logback-classic 使用testCompile的打包問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07