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