Java虛擬機的體系結構

Java Java虛擬機 編程語言 C語言 隨風ylc 2017-06-10

剛才已經提到,JVM可以由不同的廠商來實現。由於廠商的不同必然導致JVM在實現上的一些不同,然而JVM還是可以實現跨平臺的特性,這就要歸功於設計JVM時的體系結構了。

我們知道,一個JVM實例的行為不光是它自己的事,還涉及到它的子系統、存儲區域、數據類型和指令這些部分,它們描述了JVM的一個抽象的內部體系結構,其目的不光規定實現JVM時它內部的體系結構,更重要的是提供了一種方式,用於嚴格定義實現時的外部行為。每個JVM都有兩種機制,一個是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統;另外的一個負責執行包含在已裝載的類或接口中的指令,叫做運行引擎。每個JVM又包括方法區、堆、 Java棧、程序計數器和本地方法棧這五個部分,這幾個部分和類裝載機制與運行引擎機制一起組成的體系結構圖為:

Java虛擬機的體系結構

圖1 JVM的體系結構

JVM的每個實例都有一個它自己的方法域和一個堆,運行於JVM內的所有的線程都共享這些區域;當虛擬機裝載類文件的時候,它解析其中的二進制數據所包含的類信息,並把它們放到方法域中;當程序運行的時候,JVM把程序初始化的所有對象置於堆上;而每個線程創建的時候,都會擁有自己的程序計數器和 Java棧,其中程序計數器中的值指向下一條即將被執行的指令,線程的Java棧則存儲為該線程調用Java方法的狀態;本地方法調用的狀態被存儲在本地方法棧,該方法棧依賴於具體的實現。

下面分別對這幾個部分進行說明。

執行引擎處於JVM的核心位置,在Java虛擬機規範中,它的行為是由指令集所決定的。儘管對於每條指令,規範很詳細地說明了當JVM執行字節碼遇到指令時,它的實現應該做什麼,但對於怎麼做卻言之甚少。Java虛擬機支持大約248個字節碼。每個字節碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集相當於Java程序的彙編語言。

Java指令集中的指令包含一個單字節的操作符,用於指定要執行的操作,還有0個或多個操作數,提供操作所需的參數或數據。許多指令沒有操作數,僅由一個單字節的操作符構成。

虛擬機的內層循環的執行過程如下:

do{

取一個操作符字節;

根據操作符的值執行一個動作;

}while(程序未結束)

由於指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利於提高執行的效率。指令中操作數的數量和大小是由操作符決定的。如果操作數比一個字節大,那麼它存儲的順序是高位字節優先。例如,一個16位的參數存放時佔用兩個字節,其值為:

第一個字節*256+第二個字節字節碼。

指令流一般只是字節對齊的。指令tableswitch和lookup是例外,在這兩條指令內部要求強制的4字節邊界對齊。

對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

Java的堆是一個運行時數據區,類的實例(對象)從中分配空間,它的管理是由垃圾回收來負責的:不給程序員顯式釋放對象的能力。Java不規定具體使用的垃圾回收算法,可以根據系統的需求使用各種各樣的算法。

Java方法區與傳統語言中的編譯後代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯後的java代碼)和符號表。在當前的Java實現中,方法代碼不包括在垃圾回收堆中,但計劃在將來的版本中實現。每個類文件包含了一個Java類或一個Java界面的編譯後的代碼。可以說類文件是 Java語言的執行代碼文件。為了保證類文件的平臺無關性,Java虛擬機規範中對類文件的格式也作了詳細的說明。其具體細節請參考Sun公司的Java 虛擬機規範。

Java虛擬機的寄存器用於保存機器的運行狀態,與微處理器中的某些專用寄存器類似。Java虛擬機的寄存器有四種:

1.pc: Java程序計數器;

2.optop: 指向操作數棧頂端的指針;

3.frame: 指向當前執行方法的執行環境的指針;。

4.vars: 指向當前執行方法的局部變量區第一個變量的指針。

在上述體系結構圖中,我們所說的是第一種,即程序計數器,每個線程一旦被創建就擁有了自己的程序計數器。當線程執行Java方法的時候,它包含該線程正在被執行的指令的地址。但是若線程執行的是一個本地的方法,那麼程序計數器的值就不會被定義。

Java虛擬機的棧有三個區域:局部變量區、運行環境區、操作數區。

局部變量區

每個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數和雙精度浮點數佔據了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點數,那麼它實際佔據了索引n和n+1所代表的存儲空間)虛擬機規範並不要求在局部變量中的64位的值是64位對齊的。虛擬機提供了把局部變量中的值裝載到操作數棧的指令,也提供了把操作數棧中的值寫入局部變量的指令。

運行環境區

在運行環境中包含的信息用於動態鏈接,正常的方法返回以及異常捕捉。

動態鏈接

運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用於支持方法代碼的動態鏈接。方法的class文件代碼在引用要調用的方法和要訪問的變量時使用符號。動態鏈接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符號,並把變量訪問翻譯成與這些變量運行時的存儲結構相應的偏移地址。動態鏈接方法和變量使得方法中使用的其它類的變化不會影響到本程序的代碼。

正常的方法返回

如果當前方法正常地結束了,在執行了一條具有正確類型的返回指令時,調用的方法會得到一個返回值。執行環境在正常返回的情況下用於恢復調用者的寄存器,並把調用者的程序計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然後在調用者的執行環境中繼續執行下去。

異常捕捉

異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用。程序使用了throw語句。

當異常發生時,Java虛擬機採取如下措施:

·檢查與當前方法相聯繫的catch子句表。每個catch子句包含其有效指令範圍,能夠處理的異常類型,以及處理異常的代碼塊地址。

·與異常相匹配的catch子句應該符合下面的條件:造成異常的指令在其指令範圍之內,發生的異常類型是其能處理的異常類型的子類型。如果找到了匹配的catch子句,那麼系統轉移到指定的異常處理塊處執行;如果沒有找到異常處理塊,重複尋找匹配的catch子句的過程,直到當前方法的所有嵌套的 catch子句都被檢查過。

·由於虛擬機從第一個匹配的catch子句處繼續執行,所以catch子句表中的順序是很重要的。因為Java代碼是結構化的,因此總可以把某個方法的所有的異常處理器都按序排列到一個表中,對任意可能的程序計數器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計數器值下發生的異常情況。

·如果找不到匹配的catch子句,那麼當前方法得到一個"未截獲異常"的結果並返回到當前方法的調用者,好像異常剛剛在其調用者中發生一樣。如果在調用者中仍然沒有找到相應的異常處理塊,那麼這種錯誤將被傳播下去。如果錯誤被傳播到最頂層,那麼系統將調用一個缺省的異常處理塊。

操作數棧區

機器指令只從操作數棧中取操作數,對它們進行操作,並把結果返回到棧中。選擇棧結構的原因是:在只有少量寄存器或非通用寄存器的機器(如 Intel486)上,也能夠高效地模擬虛擬機的行為。操作數棧是32位的。它用於給方法傳遞參數,並從方法接收結果,也用於支持操作的參數,並保存操作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數將從堆棧彈出、相加,並把結果壓回到操作數棧中。

每個原始數據類型都有專門的指令對它們進行必須的操作。每個操作數在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。操作數只能被適用於其類型的操作符所操作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則是非法的。在Sun的虛擬機實現中,這個限制由字節碼驗證器強制實行。但是,有少數操作(操作符dupe和swap),用於對運行時數據區進行操作時是不考慮類型的。

本地方法棧,當一個線程調用本地方法時,它就不再受到虛擬機關於結構和安全限制方面的約束,它既可以訪問虛擬機的運行期數據區,也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個C語言的棧,那麼當C程序調用C函數時,函數的參數以某種順序被壓入棧,結果則返回給調用函數。在實現Java虛擬機時,本地方法接口使用的是C語言的模型棧,那麼它的本地方法棧的調度與使用則完全與C語言的棧相同。

相關推薦

推薦中...