JVM體系結(jié)構(gòu)與工作方式
JAVA能夠跨越計算機硬件組成差異和操作系統(tǒng)的差異在不同的主機上運行间校,主要就是JVM屏蔽了各個主機之間硬件和軟件的差異花鹅,使得Java與平臺的耦合性交給JVM來解決魔熏。爪哇島的第二站就是從宏觀角度上介紹JVM的體系結(jié)構(gòu)和工作方式歇父。
目錄
JVM體系結(jié)構(gòu)
JVM是個什么東西?
JVM的全稱是Java Viutual Machine,也就是Java虛擬機,它運行在一個實際的計算機上并能仿真模擬出各種計算機的功能绒极。我們先來看一下一個真實的計算機如何才能具備計算的功能,從以計算為中心的計算機體系結(jié)構(gòu)來看蔬捷,他應(yīng)該具有以下幾部分:
- 指令集:這個計算機所能識別的機器語言的命令集合垄提。
- 計算單元:能夠識別并且控制指令執(zhí)行的功能模塊。
- 尋址方式:地址的位數(shù)周拐,最小地址和最大地址范圍铡俐,以及地址的運行規(guī)則。
- 寄存器定義:對操作數(shù)寄存器速妖、變址寄存器高蜂、控制寄存器等的定義、數(shù)量和使用方式罕容。
- 存儲單元:能夠存儲操作數(shù)和保存操作結(jié)構(gòu)的單元备恤,如內(nèi)核級緩存、內(nèi)存和磁盤等等锦秒。
上述五個部分中與代碼相關(guān)程度最大的還是指令集部分露泊,什么是指令集?他又有什么作用呢旅择?指令集是在CPU中用來計算和控制計算機系統(tǒng)的一套指令的集合惭笑,它是體現(xiàn)CPU性能的一個重要標(biāo)志。那指令集與匯編語言又有什么關(guān)系生真?指令集是可以直接被機器識別的機器碼沉噩,它必須以二進制格式存儲在計算機當(dāng)中,而匯編語言是能夠被人所識別的指令柱蟀,它在邏輯和順序上是與機器指令一一對應(yīng)的川蒙。
每個運行的Java程序都是一個JVM實例,JVM和實體機一樣也有一套合適的指令集长已,這個指令集能夠被JVM解析執(zhí)行畜眨,這個指令集我們稱為JVM字節(jié)碼指令集昼牛。符合JVM規(guī)范的class文件字節(jié)碼都可以被JVM執(zhí)行。
Java代碼執(zhí)行流程圖
JVM體系機構(gòu)詳解
除了指令集之外康聂,JVM還需要以下四個組成部分:
- 類加載器:在JVM啟動時或者在類運行時將需要的class加載到JVM中并解析成JVM統(tǒng)一使用的對象格式贰健。
- 執(zhí)行引擎:執(zhí)行引擎的任務(wù)是負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令,相當(dāng)于CPU恬汁。
- 內(nèi)存區(qū):將內(nèi)存劃分為若干個區(qū)以模擬實際機器上的存儲伶椿、記錄和調(diào)度功能模塊。例如方法區(qū)蕊连,堆區(qū)悬垃,棧區(qū)和常量池等游昼。
- 本地方法調(diào)用:調(diào)用C或C++實現(xiàn)的本地方法的代碼返回結(jié)果甘苍。
類加載器
類加載器作為JVM的對外接口,具有三個主要的功能:1.將class文件加載到JVM當(dāng)中烘豌;2.將Class字節(jié)碼重新解析成JVM統(tǒng)一要求的對象格式载庭;3.審查每個類應(yīng)該由誰來加載(是一種父優(yōu)先的等級加載機制)。
執(zhí)行引擎
執(zhí)行引擎是JVM的核心部分廊佩,執(zhí)行引擎的作用就是解析JVM字節(jié)碼指令囚聚,得到執(zhí)行結(jié)果。執(zhí)行引擎也就是執(zhí)行一條條代碼的一個流程标锄,每個Java線程都是一個執(zhí)行引擎的實例顽铸。執(zhí)行引擎一般有兩種執(zhí)行方式:
直接解釋執(zhí)行:解釋器會將代碼一句一句的進行解釋,沒解釋一句就運行一句料皇,在這個過程中不會產(chǎn)生機器碼文件谓松。
JIT(即時編譯器)執(zhí)行:先編譯再執(zhí)行,需要編譯器先將字節(jié)碼編譯成機器碼践剂,然后再進行執(zhí)行鬼譬,該過程會產(chǎn)生機器碼文件.
在實際JVM當(dāng)中會對代碼進行分類判斷,對于熱點代碼(多次調(diào)用的代碼和多次執(zhí)行的循環(huán)體)做編譯逊脯,非熱點代碼做解釋執(zhí)行优质。
Java內(nèi)存管理
JVM的執(zhí)行引擎在執(zhí)行一段程序時會存儲一些東西,比如操作碼需要的操作數(shù)军洼,操作碼的執(zhí)行結(jié)果等等巩螃。具體JVM的內(nèi)存區(qū)域管理和劃分會在下一篇文章進行解釋!
JVM工作方式
前面簡單分析了JVM的基本結(jié)構(gòu)匕争,下面再簡單分析一下JVM是如何執(zhí)行字節(jié)碼命令的避乏,也就是前面介紹的執(zhí)行引擎是如何工作的。
機器如何執(zhí)行代碼
我們知道機器語言一般都是和硬件平臺息息相關(guān)的汗捡,而高級語言一般都是屏蔽了所有的硬件平臺和軟件平臺(例如操作系統(tǒng))淑际。之所以能夠屏蔽就是有了編譯器畏纲,可以將高級語言轉(zhuǎn)化為CPU可以直接執(zhí)行的機器碼。通常一個程序從編寫到執(zhí)行會經(jīng)歷以下一些階段:
源代碼——>預(yù)處理器——>編譯器——>匯編程序——>目標(biāo)代碼——>鏈接器——>可執(zhí)行程序
除了源代碼和最后的可執(zhí)行程序春缕,中間的所有環(huán)節(jié)都是由現(xiàn)代意義上的編譯器統(tǒng)一完成的盗胀。
(值得注意的是,我們通常所說的編譯器都是將某種高級語言直接編譯成可執(zhí)行的目標(biāo)機器語言锄贼。但是實際上還有一些編譯器是將一種高級語言編譯成另外一種高級語言票灰,或者將低級語言編譯成高級語言(反編譯),或者將高級語言編譯成虛擬機目標(biāo)語言宅荤,如Java編譯器等屑迂。)
再回到如何讓機器(不論是虛擬機還是實體機)執(zhí)行代碼的主題,不論執(zhí)行什么代碼都可以進一步分解為二進制的位運算冯键,這些運算的核心目的就是確定需要運算的種類(操作碼)和運算的數(shù)據(jù)(操作數(shù))惹盼,以及從哪里獲取操作數(shù)(寄存器或者棧)和將運算結(jié)果存放到什么地方(寄存器或者棧)。因此基于兩種不同的存放方式(寄存器和棧)惫确,指令集會有基于寄存器的架構(gòu)實現(xiàn)和基于棧的架構(gòu)實現(xiàn)兩種方式手报。JVM選擇的是基于棧的架構(gòu)。
JVM為何選擇基于棧的架構(gòu)
JVM執(zhí)行字節(jié)碼指令是基于棧的架構(gòu)改化,也就是所有的操作數(shù)都必須先入棧掩蛤,然后再根據(jù)指令中的操作碼選擇從棧頂彈出若干個元素進行計算之后再壓入棧中。這就和一般的基于寄存器的操作有所不同陈肛,一個操作需要頻繁的入棧和出棧揍鸟,如果是基于寄存器(CPU的存儲器,讀取數(shù)據(jù)速度較快)的話不需要這么多的移動數(shù)據(jù)的操作句旱,那么為什么JVM仍然選擇基于棧的架構(gòu)呢阳藻?
主要的原因就是JVM要設(shè)計成平臺無關(guān)的,而平臺無關(guān)性就是要保證再沒有或者很少的寄存器的機器上也要同樣能正確執(zhí)行Java代碼前翎,而基于寄存器的架構(gòu)過于依賴于寄存器本身的硬件特性稚配,雖然基于寄存器可能會提高性能,但很大程度上犧牲了跨平臺的移植性港华,很難設(shè)計出一款通用的基于寄存器的指令道川,綜上所述,JVM最終選擇了基于棧的架構(gòu)立宜!
執(zhí)行引擎的架構(gòu)設(shè)計
了解了Java以棧為架構(gòu)的原因之后冒萄,再詳細(xì)看下JVM是如何設(shè)計Java的執(zhí)行部件的。
見下圖:
Java執(zhí)行部件
image
每當(dāng)創(chuàng)建了一個新的線程時橙数,JVM會為這個線程創(chuàng)建一個Java棧尊流,同時會為這個線程分配一個PC寄存器,并且此時這個PC寄存器會指向這個線程的第一行可執(zhí)行代碼(隨著線程的執(zhí)行灯帮,PC寄存器保持指向當(dāng)前正在執(zhí)行的代碼)崖技。每當(dāng)線程調(diào)用一個新方法時逻住,會在這個棧上創(chuàng)建一個新的棧幀數(shù)據(jù)結(jié)構(gòu),這個棧幀會保留這個方法的一些元信息迎献,如方法中定義的局部變量瞎访,正常方法返回以及異常處理機制等。JVM在調(diào)用某些指令時可能需要調(diào)用到常量池中的一些常量吁恍,Java對象和構(gòu)造函數(shù)等都存儲在所有線程共享的方法區(qū)和Java堆中扒秸。
執(zhí)行引擎的執(zhí)行過程
以一個方法的執(zhí)行過程解釋執(zhí)行引擎字節(jié)碼的指令流程:
public class Math{
public static void main(String args[])
{
int a=1;
int b=2;
int c=(a+b)*10;
}
}
其中main的字節(jié)碼指令如下:
偏移量 指令 說明
0: iconst_1 常數(shù)1入棧
1: istore_1 將棧頂元素移入本地變量1存儲
2: iconst_2 常數(shù)2入棧
3: istore_2 將棧頂元素移入本地變量2存儲
4: iload_1 本地變量1入棧
5: iload_2 本地變量2入棧
6: iadd 彈出棧頂兩個元素相加之后將結(jié)果再入棧
7: bipush 10 將10壓入棧
8: imul 彈出棧頂兩個元素相乘之后再將結(jié)果入棧
9: istore_3 棧頂元素移入本地變量3存儲
10: return 返回
第十條指令執(zhí)行完,當(dāng)前的這個方法對應(yīng)的這些部件會被JVM回收冀瓦,局部變量區(qū)的所有值將全部釋放伴奥,PC寄存器會被銷毀,在Java棧中與這個方法對應(yīng)的棧幀將消失翼闽。
參考資料:
參考鏈接博客最下方
許令波——深入分析Java Web技術(shù)內(nèi)幕(第七章)