java虛擬機初探

首先來看一下Java程序的執行過程java

首先是.java源文件被編譯成爲.class類文件(又叫字節碼),JRE的類加載器從硬盤中讀取class文件,載入到系統分配給JVM的內存區域--運行數據區(Runtime Data Areas). 而後執行引擎解釋或者編譯類文件,轉化成特定CPU的機器碼,CPU執行機器碼,至此完成整個過程.上面圖瞭解明白後再看下面這個圖應該理解更深刻一些。程序員

接下來是類加載器算法

下面是類加載器的層級結構,也就是父子關係。Bootstrap是全部類加載器的父親。數組

--Bootstrap class loader:安全

當運行java虛擬機時,這個類加載器被建立,它加載一些基本的java API,包括Object這個類。須要注意的是,這個類加載器不是用java語言寫的,而是用C/C++寫的。函數

--Extension class loader:性能

這個加載器加載除了基本API以外的一些拓展類,包括一些與安全性能相關的類。(目前瞭解得不是很深,只能籠統說,待往後再詳細說明)spa

 

--System Class Loader:.net

它加載應用程序中的類,也就是在你的classpath中配置的類。命令行

--User-Defined Class Loader:

這是開發人員經過拓展ClassLoader類定義的自定義加載器,加載程序員定義的一些類。

 

 

 

》》委派模式(Delegation Mode)

仔細看上面的層次結構,當JVM加載一個類的時候,下層的加載器會將任務委託給上一層類加載器,上一層加載檢查它的命名空間中是否已經加載這個 類,若是已經加載,直接使用這個類。若是沒有加載,繼續往上委託直到頂部。檢查完了以後,按照相反的順序進行加載,若是Bootstrap加載器找不到這 個類,則往下委託,直到找到類文件。對於某個特定的類加載器來講,一個Java類只能被載入一次,也就是說在Java虛擬機中,類的完整標識是 (classLoader,package,className)。一個類能夠被不一樣的類加載器加載。

 

舉個具體的例子來講明,如今加入我有一個本身定義的類MyClass須要加載,若是不指定的話,通常交App(System)加載。接到任務 後,System檢查本身的庫裏是否已經有這個類,發現沒有以後委託給Extension,Extension進行一樣的檢查,發現仍是沒有繼續往上委 託,最頂層的Boots發現本身庫裏也沒有,因而根據它的路徑(Java 核心類庫,如java.lang)嘗試去加載,沒找到這個MaClass類,因而只好(人家看好你,交給你完成,你無能爲力,只好交給別人啦)往下委託給 Extension,Extension到本身的路徑(JAVA_HOME/jre/lib/ext)是找,仍是沒找到,繼續往下,此時System加載 器到classpath路徑尋找,找到了,因而加載到Java虛擬機。

如今假設咱們將這個類放到JAVA_HOME/jre/lib/ext這個路徑中去(至關於交給Extension加載器加載),按照一樣的規則, 最後由Extension加載器加載MyClass類,看到了吧,統一各種被兩次加載到JVM,可是每次都是由不一樣的ClassLoader完成。

 

 

 

》》可見性限制

下層的加載器可以看到上層加載器中的類,反之則不行,也就是是說委託只能從下到上。

 

》》不容許卸載類

類加載器能夠加載一個類,可是它不能卸載一個類。可是類加載器能夠被刪除或者被建立。

 

 

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

 

 

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語言的棧相同。

 

3  Java虛擬機的運行過程

 

上面對虛擬機的各個部分進行了比較詳細的說明,下面經過一個具體的例子來分析它的運行過程。

虛擬機經過調用某個指定類的方法main啓動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時連接該類所使用的其它的類型,而且初始化它們。例如對於程序:

class HelloApp

{ public static void main(String[] args) { System.out.println("Hello World!");

for (int i = 0; i < args.length; i++ ) {

System.out.println(args[i]);

} } }

 

編譯後在命令行模式下鍵入: java HelloApp run virtual machine

將經過調用HelloApp的方法main來啓動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。如今咱們略述虛擬機在執行HelloApp時可能採起的步驟。

開始試圖執行類HelloApp的main方法,發現該類並無被裝載,也就是說虛擬機當前不包含該類的二進制表明,因而虛擬機使用ClassLoader試圖尋找這樣的二進制表明。若是這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用以前,必須對類HelloApp與其它類型進行連接而後初始化。連接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則建立類或接口的靜態域以及把這些域初始化爲標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化以前它的父類必須被初始化。整個過程以下

 

圖4:虛擬機的運行過程

4  結束語

 

本文經過對JVM的體系結構的深刻研究以及一個Java程序執行時虛擬機的運行過程的詳細分析,意在剖析清楚Java虛擬機的機理。

相關文章
相關標籤/搜索