引言
Java虛擬機可以看做是一臺抽象的計算機,如同真實的計算機那樣,有自己的指令集以及各種運行時內(nèi)存區(qū)域。Java虛擬機與Java語言沒有必然聯(lián)系,它只與特定的二進制文件即Class文件關(guān)聯(lián),Class文件包含了Java虛擬機指令(字節(jié)碼),符號表以及一些需要的輔助信息。任何一種語言只要可以被編譯成有效的Class文件,都可以在Java虛擬機上面運行。
1.數(shù)據(jù)類型
Java虛擬機可以操作的數(shù)據(jù)類型分為兩類,原始類型(Primitive Types)與引用類型(Reference Types)。原始類型不需要額外的手段來確定運行期他們實際的數(shù)據(jù)類型,指令本身就可以確定;引用類型編譯器應(yīng)當(dāng)在編譯期間盡最大努力完成類型檢查。
2.原始類型與值
原始類型包括數(shù)值類型(Numberic Types)、布爾類型(Boolean Type)和returnAddress類型。
數(shù)值類型:整數(shù)類型byte short int long char,浮點類型float double,與IEEE 754格式取值和操作一致。
布爾類型:Java虛擬機定義了boolean這種數(shù)據(jù)類型,但是沒有指令支持,涉及到boolean值類型的運算,都會被編譯成int類型來代替。
returnAddress類型:被指令jsr,jsr_w,ret使用,從JDK7開始虛擬機不允許出現(xiàn)這幾條指令,所以不用過于關(guān)注。returnAddress類型的值指向一條虛擬機指令的操作碼,初衷是用來實現(xiàn)Java語言中的finally語句塊。jsr與ret是一起使用的,jsr跳轉(zhuǎn)到指定的offet位置,并將jsr下一條指令壓入棧頂,就是retureAddress類型了,使用ret返回到指定的指令位置。參考:)
3.引用類型與值
類類型(Class Types) 數(shù)組類型(Array Types) 接口類型(Interface Types),分別對應(yīng)類實例,數(shù)組實例,實現(xiàn)某個接口的實例。
引用類型值有一個特殊的值null,當(dāng)一個引用不指向任何對象時,它的值用null表示,可以轉(zhuǎn)換為任意類型,Java虛擬機沒有規(guī)定null的實現(xiàn)應(yīng)用用怎樣的編碼。
4.運行時數(shù)據(jù)區(qū)

PC(Program Counter)寄存器:Java虛擬機中每一條線程都有自己的PC寄存器,用來保存當(dāng)前方法的指令地址(也就是returenAddress類型的值),如果方法是native的,則保存本地指針的值。
Java虛擬機棧(Java Virtual Machine Stack):Java虛擬機每一條線程都有私有的棧,用來存儲局部變量與一些過程結(jié)果的地方,由棧幀(Frames)組成。Java虛擬機棧能夠被實現(xiàn)成固定大小或者動態(tài)擴展模型。異常情形:(1)如果線程請求分配的棧容量超過Java虛擬機棧允許的最大容量,則拋出StackOverflowError異常 (2)如果Java虛擬機棧能夠動態(tài)擴展,申請不到內(nèi)存去創(chuàng)建新的棧,則拋出OutofMemoryError異常。
**Java堆(Heap) **:堆區(qū)是線程共享的區(qū)域,用分配類實例,數(shù)組對象的內(nèi)存區(qū)域。Java虛擬機啟動的時候就會被創(chuàng)建,并且分配的內(nèi)存由GC(Garbage Collector)管理,這些對象無需也無法顯示地被銷毀。當(dāng)創(chuàng)建的堆超過了GC能夠提供的容量,則會拋OutofMemoryError異常。
方法區(qū)(Method Aera):Java虛擬機啟動時創(chuàng)建,線程共享的內(nèi)存區(qū)域,編譯代碼(類信息,類方法,成員變量,運行時常量等信息)的存儲區(qū)域。雖然方法區(qū)是堆區(qū)的邏輯組成部分,虛擬機實現(xiàn)可以選擇是否回收該區(qū)域垃圾。方法區(qū)內(nèi)存空間不滿足內(nèi)存分配要求,同樣拋出OutOfMemoryError。
運行時常量池(Rumtime Constant Pool): 類似符號表,從編譯時可以知道的字面量到必須運行是解析后才能知道的方法或者字段引用,位于方法區(qū)中,在類和接口被創(chuàng)建,對應(yīng)的運行時常量池就被創(chuàng)建。
本地方法棧(Native Method Stack): 用來支持native方法的執(zhí)行,在線程創(chuàng)建時分配。和虛擬機棧類似,能夠動態(tài)擴展,棧容量超過本地方法棧允許最大容量拋StackOverflowError,無法申請到足夠的內(nèi)存去擴展拋OutOfMemoryError。
4.棧幀(Frame)
用來存儲數(shù)據(jù)或部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),處理動態(tài)鏈接(Dynamic Linking)、方法返回值、異常分派(Dispatch Exception)。隨著方法調(diào)用創(chuàng)建,方法結(jié)束銷毀。每一個棧幀都有自己的局部變量表(Local Variables),操作數(shù)棧(Operand Stack)和指向當(dāng)前方法所屬的類的運行時常量池的引用,并且容量是在編譯期確定的。
棧幀是線程本地私有數(shù)據(jù),不可能在一個棧幀之中訪問另一條線程的棧幀。
局部變量表:局部變量表可以保存前面所述的虛擬機的數(shù)據(jù)類型,其中兩個局部變量保存一個類型為long或double的數(shù)據(jù)。局部變量表使用索引來訪問,可以想象為一個數(shù)組的模型,當(dāng)方法調(diào)用時,它的參數(shù)從零開始連續(xù)的存放在局部變量表示,如果是實例方法,則第0個局部變量一定是調(diào)用方法對象的引用(即Java里的this)。
操作數(shù)棧:后進先出(Last-In-First-Out,LIFO)棧,用來存放Java虛擬的指令執(zhí)行時操作數(shù)以及執(zhí)行后的結(jié)果,操作數(shù)棧與局部變量表可以相互轉(zhuǎn)移。在方法調(diào)用的時候,操作數(shù)棧用來準備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。
每一個棧幀內(nèi)部都包含一個指向運行時常量池的引用來支持當(dāng)前方法的代碼實現(xiàn)動態(tài)鏈接,方法調(diào)用或者訪問成員變量時是通過符號引用表示,動態(tài)鏈接的作用就是將符號引用轉(zhuǎn)化為實際的方法引用。
5.浮點算法
Java虛擬機中浮點操作在遇到非法操作(如被零整除,上限溢出等)不會拋exception。
6.初始化方法
Java虛擬機層面上類實例的構(gòu)造方法名為<init>,在實例的初始化通過invokespecial指令調(diào)用。類和接口的初始化通過<clinit>,在類加載時由Java虛擬機自身隱式調(diào)用,沒有任何指令可以調(diào)用這個方法。
7.異常
異常的本質(zhì)是程序控制權(quán)的一種及時、非局部的轉(zhuǎn)換(從異常拋出的地方轉(zhuǎn)至處理異常的地方)。當(dāng)前前程拋出的異常稱為同步異常,非當(dāng)前線程拋出的異常為異步異常。虛擬機異常的情形有:
- 指令非正常執(zhí)行,如數(shù)組越界,棧溢出等
- athrow指令被執(zhí)行
- 虛擬機內(nèi)部錯誤或者Thread/ThreadGroup的stop方法被執(zhí)行(異步異常)
Java虛擬機執(zhí)行每一個方法都會配有零至多個異常處理器(Exception Handlers),每個方法的異常處理器都存儲在一個表中,在運行時出現(xiàn)異常后,會按照異常處理器的描述執(zhí)行。
8.字節(jié)碼指令集簡介
Java虛擬機的指令有一個字節(jié)長度的操作碼(Opcode)和操作數(shù)(Operands)組成,由于操作碼為一個字節(jié),所以虛擬機的字節(jié)碼指令最后有256條。
Java虛擬機解釋器偽代碼:
do {
自動計算PC寄存器以及從PC寄存器的位置取出操作碼
if(存在操作數(shù))取出操作數(shù)
執(zhí)行操作碼所定義的操作
}while(處理下一次循環(huán));
由于Java虛擬機字節(jié)碼數(shù)量限制,對于特定類型操作只提供了有限的類型相關(guān)指令去操作它。多數(shù)對于boolean byte short char類的數(shù)據(jù)操作,實際上都是使用相應(yīng)對int類型作為運算類型。
加載存儲指令:用于局部變量表與操作數(shù)棧之間來回傳輸,例如:
istore_1 指令作用是從操作數(shù)棧中彈出一個int型的值,并保存在第一個局部變量中
iload_1 指令作用是將第一個局部變量的值壓入操作數(shù)棧
運算指令:用于兩個操作數(shù)棧上的值進行運算,并把結(jié)果重新存入操作數(shù)棧棧頂。例如iadd isub,Java虛擬機沒有明確規(guī)定整型數(shù)據(jù)溢出情況,但規(guī)定除法指令(idiv/ldiv),求余指令(irem和lrem)的除數(shù)為零時拋ArithmeitcException異常。
類型轉(zhuǎn)換指令:Java虛擬機直接支持寬化類型轉(zhuǎn)換(Widening Numberic Conversions),如int類型到long float double類型,long 到float double類型。 窄化類型轉(zhuǎn)換(Narrowing Numberic Conversions)會導(dǎo)致符號位丟失,精度丟失,指令有i2b i2c f2i f2l等等。
對象創(chuàng)建與操作:類實例與數(shù)組都是對象,使用不同的指令操作。
創(chuàng)建對象new,創(chuàng)建數(shù)組newarray anewarray multinewarray。
訪問字段getfield putfield getstatic putstatic
加載數(shù)組元素到操作數(shù)棧:iaload aaload等
將操作數(shù)棧的值存儲到數(shù)組元素:iastore aastore等
取數(shù)組長度的指令arraylength
檢查類實例類型的指令instanceof checkcast
操作數(shù)棧管理:pop pop2 dup dup2 swap等
控制轉(zhuǎn)移指令:
條件分支:ifeq iflt...
復(fù)合條件分支:tableswitch lookupswitch
無條件:goto goto_w jsr ret..
方法調(diào)用和返回指令:四條指令用于方法調(diào)用,
invokevirtial 調(diào)用實例方法
invokeinterface 調(diào)用接口方法
invokespecial 調(diào)用特殊的實例方法,例如實例初始化方法,私有方法以及父類方法
invodestatic 調(diào)用靜態(tài)方法
方法返回指令 ireturn(同樣,boolean byte char short int類型時時候) areturn return(返回類型類void)
拋出異常:顯式拋出的指令athrow,Java虛擬機檢測到指令執(zhí)行異常由Java虛擬機自動拋出。
同步:方法級同步時隱式的,常量池的方法列表里面指令。指令序列同步的關(guān)鍵字 monitorennter monitorexit,對應(yīng)Java中sychronized的代碼塊。
8.類庫
Java虛擬機必須對不同平臺下的Java類庫提供充分的實現(xiàn),某些與操作系統(tǒng)密切相關(guān)的類庫需要Java虛擬機的本地方法來實現(xiàn):
- 反射 java.lang.relect包與java.lang.Class類
- 類和接口的加載與創(chuàng)建 java.lang.ClassLoader類
- 安全相關(guān) java.lang.SecurityManager
- 多線程
- 弱引用
9.公有設(shè)計,私有實現(xiàn)
虛擬機實現(xiàn)必須能夠讀取Class文件,并且精確實現(xiàn)虛擬機代碼的含義,怎么實現(xiàn)是實現(xiàn)者自己的事情,只要外部接口看起來與規(guī)范描述的一樣。目前虛擬機實現(xiàn)方式主要有兩種:
- 將輸入的Java虛擬代碼在加載的時或執(zhí)行時翻譯成另外一種虛擬機指令集。(如Davilk)
- 將輸入的Java虛擬機代碼在加載時或執(zhí)行時翻譯成宿主機CPU的本地指令集(如ART)