全面認(rèn)識JVM結(jié)構(gòu)組成
你對JVM結(jié)構(gòu)是否了解,這里和大家分享一下,首先看一下類文件格式,JVM使用一種硬件、操作系統(tǒng)無關(guān)的二進(jìn)制格式來保存編譯后的代碼。
JVM結(jié)構(gòu)
類文件格式
JVM使用一種硬件、操作系統(tǒng)無關(guān)的二進(jìn)制格式來保存編譯后的代碼。
JVM結(jié)構(gòu)之?dāng)?shù)據(jù)類型
和Java語言一樣,JVM操作兩種數(shù)據(jù)類型:基本類型和引用類型。
類型檢驗(yàn)應(yīng)該在編譯期完成,JVM不需要負(fù)責(zé)類型檢驗(yàn)。
JVM根據(jù)指令來分辨操作數(shù)的類型:
iadd->int
ladd->long
fadd->float
dadd->double
JVM顯式的支持“對象”的概念。一個對象可以使一個動態(tài)分配的對象實(shí)例或一個數(shù)組。一個引用擁有引用類型。引用類型可以理解為指向?qū)ο蠡驍?shù)組的指針??梢酝瑫r(shí)有多個引用指向一個對象實(shí)例或數(shù)組。對象實(shí)例和數(shù)組總是通過引用被操作,傳遞和測試(Testedviavaluesoftypereference)。
returnAddress類型
JVM中的returnAddress類型是jsr,ret和jsr_w指令。returnAddress類型是指向JVM操作碼的指針。returnAddress類型不是簡單意義上的數(shù)值,不屬于任何一種基本類型。Java程序無法動態(tài)地修改returnAddress。
boolean類型
JVM只boolean類型提供有限的支持。沒有單獨(dú)的JVM指令單獨(dú)操作boolean值,Java源代碼中對boolean類型變量的操作被編譯為int類型的指令。JVM不直接的支持boolean類型的數(shù)組,而是使用操作byte數(shù)組的指令來操作boolean數(shù)組。比如baload,bastore。Java編譯器將Java語言的true和false映射為JVM中的int類型的1和0。
JVM結(jié)構(gòu)之引用類型
引用類型包括三中:classtypes,arraytypes,interfacetypes。他們的值指向動態(tài)創(chuàng)建的類對象,數(shù)組或?qū)崿F(xiàn)接口的類對象。一個引用可以是空的(null)??找貌恢赶蛉魏螌ο???找貌粚儆谌魏晤愋停强梢员晦D(zhuǎn)換成任何類型。JVMSpec不強(qiáng)制要求null在字節(jié)碼中為某個值,如“0”。
#p#
運(yùn)行時(shí)數(shù)據(jù)區(qū)域(RuntimeDataAreas)
JVM定義了一組運(yùn)行時(shí)數(shù)據(jù)區(qū)域。這些區(qū)域再JVM運(yùn)行程序時(shí)使用。一些區(qū)域在JVM啟動的時(shí)候就被創(chuàng)建,在JVM關(guān)閉時(shí)銷毀。還有些區(qū)域是每個線程所有的。線程啟動時(shí)創(chuàng)建,線程結(jié)束時(shí)銷毀。
JVM結(jié)構(gòu)之pc寄存器
JVM支持多線程。每個線程都有自己的pc(programcounter)寄存器。任意時(shí)刻JVM線程執(zhí)行某個方法的代碼。如果方法不是native的,那么pc指向當(dāng)前執(zhí)行的JVM指令。如果是native的,那么pc必須足夠大來保存returnAddress或一個當(dāng)前pingai平臺下的本地指針。
JVM棧
每個線程都擁有一個私有的JVMstack,這個堆棧與線程一同創(chuàng)建。JVM棧和C語言的棧相似。由于JVM的Frame可以放在堆上,所以JVMstack可以是不連續(xù)的。JVM實(shí)現(xiàn)者應(yīng)該讓程序員可以控制初始棧的大小,并控制棧的最大最小值。JVMstack可以動態(tài)增加。
Heap
JVM有一個堆,所有JVM中的線程共享這個堆。所有的類對象實(shí)例和數(shù)組都分配在堆上。
JVM堆在JVM啟動的時(shí)候被創(chuàng)建。JVM提供一個垃圾收集者來管理堆。堆上的對象不需要程序員顯式地銷毀。堆可以是固定大小,也可以根據(jù)需要增加大小。堆可以是不連續(xù)的。
JVM結(jié)構(gòu)之方法區(qū)域(MethodArea)
JVM有一個方法區(qū)域,所有JVM中的線程共享這個區(qū)域。這個區(qū)域與C語言程序中的“text”段類似。在其中保存了每個類屬的數(shù)據(jù),比如Runtimeconstantpool,field和methoddata,還有方法的字節(jié)碼和構(gòu)造函數(shù),其中還包括類的“specialmethods”,還有實(shí)例和接口初始化代碼。
Runtimeconstantpool
一個Runtimeconstantpool是代表了一個class文件中類或接口的常量表。其中包含若干常量,從編譯期就固定的數(shù)值常量到編譯期必須決定的方法和field的引用。Runtimeconstantpool類似與C語言中的符號表。
每個Runtimeconstantpool從JVM的MethodArea中分配。Runtimeconstantpool在類或接口被JVM創(chuàng)建的時(shí)候創(chuàng)建。
NativeMethodStack
JVM可以使用傳統(tǒng)的堆棧來支持本地方法。#p#
JVM結(jié)構(gòu)之Frame
Frame用來存儲數(shù)據(jù),部分返回結(jié)果,也用于動態(tài)連接,返回方法的結(jié)果,以及分發(fā)異常
每次調(diào)用方法,JVM都會再當(dāng)前線程的Stack上創(chuàng)建一個Frame,當(dāng)方法結(jié)束是銷毀這個Frame。
每個Frame都有自己的局部變量數(shù)組,自己的操作數(shù)棧(operandstack)。
局部變量數(shù)組和操作數(shù)棧的大小在編譯期就決定了。局部變量和操作數(shù)有當(dāng)前Frame所屬的方法提供。
Frame的大小由虛擬機(jī)的實(shí)現(xiàn)者決定。Frame所占用的內(nèi)存可以在方法調(diào)用的時(shí)分配。
每個線程運(yùn)行的某個時(shí)刻只能有一個Frame是活躍的,稱為“當(dāng)前Frame”。這個線程稱為“當(dāng)前線程”。包含這個方法的類稱為“當(dāng)前類”。當(dāng)一個方法調(diào)用了另一個方法,那么它的Frame不在活躍,被調(diào)用的方法的Frame成為“當(dāng)前Frame”。注意:兩個線程創(chuàng)建的Frame是完全獨(dú)立的。
JVM結(jié)構(gòu)之局部變量
每個Frame都有一個局部變量數(shù)組,數(shù)組的長度取決于方法的局部變量個數(shù)。
單個局部變量可以存儲:boolean,byte,char,short,int,float,reference和returenAddress
一對局部變量可以存儲:long或double
局部變量用索引值來取址。第一個局部變量的索引是0。
JVM使用局部變量來傳遞方法參數(shù)。對于類方法,方法參數(shù)從局部變量“0”(零)開始。
對于實(shí)例方法,局部變量“0”被用來保存當(dāng)前實(shí)例的引用值(this)。方法參數(shù)從局部變量“1”開始。
JVM結(jié)構(gòu)之操作數(shù)棧(stack)
每個Frame都包含一個LIFO的棧,稱為OperandStack。該棧的最大深度在編譯期決定,有創(chuàng)建Frame的方法代碼決定。
JVM需要提供將局部變量或常量壓入操作數(shù)棧的指令。其他指令可以操作棧上的數(shù)據(jù),并將結(jié)果也壓入棧。操作數(shù)棧也用于傳遞參數(shù)和接受返回值。
比如,iadd指令將兩個int值加起來。這就需要被加的兩個數(shù)在棧的最頂端。他們是由前面的指令壓入棧的。兩個數(shù)從棧中彈出。相加后的結(jié)果被壓入棧。
動態(tài)連接(DynamicLinking)
每個Frame包含一個指向當(dāng)前Runtimeconstantpool的引用,用來提供方法的動態(tài)鏈接。
方法代碼是通過符號來引用變量和調(diào)用方法的。JVM動態(tài)的將符號翻譯為具體的方法引用或變量的索引。
這就是Java實(shí)現(xiàn)晚綁定的機(jī)制。這種晚綁定使得代碼變得更安全。
JVM結(jié)構(gòu)之方法正常結(jié)束與異常結(jié)束
如果方法沒有引起或拋出任何異常,那么方法會正常結(jié)束。需要指出的是,異??梢允怯蒍VM直接拋出的,也可以是程序顯式拋出的。
初始化方法
在JVM層次上,每個類的構(gòu)造函數(shù)都有一個特殊的名字。這個名字由編譯器提供。Java語言中不能直接使用這個名字。在JVM中,通過invokespecial指令來調(diào)用這個方法。
一個類或接口最多有一個類或接口初始化方法。這個方法是靜態(tài)而且沒有任何參數(shù)的。它有一個特殊的名字:。這個名字也有編譯期提供,Java語言中不能直接用。類和接口的初始化方法有JVM隱式地調(diào)用。它們從不被某個JVM指令調(diào)用,而是作為類的初始化過程的一部分被調(diào)用。
異常
拋出異常會使當(dāng)前方法異常結(jié)束。每個類的異常Handler被放在類文件的一個表中。
當(dāng)異常發(fā)生的時(shí)候,JVM會從中找到合適的異常處理Handler來處理,如果當(dāng)前方法沒有合適的處理當(dāng)前異常的Handler,則將當(dāng)前方法的Frame彈出,扔掉Operandstack和局部變量。返回到當(dāng)前方法的調(diào)用者中,再重復(fù)前面的過程,直到到達(dá)調(diào)用鏈條的頂端。如果最外層的方法也沒有合適的Handler,就退出當(dāng)前線程。
【編輯推薦】