JVM的每一個實例都有一個它本身的方法域和一個堆,運行於JVM內的全部的線程都共享這些區域;當虛擬機裝載類文件的時候,它解析其中的二進制數據所包含的類信息,並把它們放到方法域中;當程序運行的時候,JVM把程序初始化的全部對象置於堆上;而每一個線程建立的時候,都會擁有本身的程序計數器和 Java棧,其中程序計數器中的值指向下一條即將被執行的指令,線程的Java棧則存儲爲該線程調用Java方法的狀態;本地方法調用的狀態被存儲在本地方法棧,該方法棧依賴於具體的實現。 java
2.1.1執行引擎 程序員
執行引擎處於JVM的核心位置,在Java虛擬機規範中,它的行爲是由指令集所決定的。儘管對於每條指令,規範很詳細地說明了當JVM執行字節碼遇到指令時,它的實現應該作什麼,但對於怎麼作卻言之甚少。Java虛擬機支持大約248個字節碼。每一個字節碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集至關於Java程序的彙編語言。 算法
Java指令集中的指令包含一個單字節的操做符, 用於指定要執行的操做, 還有0個或多個操做數, 提供操做所需的參數或數據。許多指令沒有操做數,僅由一個單字節的操做符構成。 數組
虛擬機的內層循環的執行過程以下: 安全
do{ 多線程
取一個操做符字節; 函數
根據操做符的值執行一個動做; spa
}while(程序未結束) .net
因爲指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利於提升執行的效率。指令中操做數的數量和大小是由操做符決定的。若是操做數比一個字節大,那麼它存儲的順序是高位字節優先。例如,一個16位的參數存放時佔用兩個字節,其值爲: 線程
第一個字節*256+第二個字節字節碼。
指令流通常只是字節對齊的。指令tableswitch和lookup是例外,在這兩條指令內部要求強制的4字節邊界對齊。
2.1.2本地方法接口
對於本地方法接口,實現JVM並不要求必定要有它的支持,甚至能夠徹底沒有。Sun公司實現Java本地接口 (JNI) 是出於可移植性的考慮,固然咱們也能夠設計出其它的本地接口來代替Sun公司的JNI。可是這些設計與實現是比較複雜的事情,須要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。
2.1.3 Runtime data area
Runtime data area 主要包括五個部分:Heap (堆), Method Area (方法區域), Java Stack (java的棧), Program Counter (程序計數器), Native method stack (本地方法棧)。Heap 和Method Area是被全部線程的共享使用的, 用來保存各類JAVA對象,好比數組,線程對象等;而Java stack, Program counter 和Native method stack是以線程爲粒度的,每一個線程獨自擁有。
Heap
Java程序在運行時建立的全部類實例或數組都是從堆中分配空間,一個Java虛擬實例中只存在一個堆空間,所以全部線程都將共享這個堆。每個java程序獨佔一個JVM實例,於是每一個java程序都有它本身的堆空間,它們不會彼此干擾。可是同一java程序的多個線程都共享着同一個堆空間,就得考慮多線程訪問對象(堆數據)的同步問題。 (這裏可能出現的異常java.lang.OutOfMemoryError: Java heap space)
堆的管理是由垃圾回收來負責的,不給程序員顯式釋放對象的能力。Java不規定具體使用的垃圾回收算法, 能夠根據系統的需求使用各類各樣的算法。
Method area
在Java虛擬機中,被裝載的class的信息存儲在Method area的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,而後讀入這個class文件內容並把它傳輸到虛擬機中。緊接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。該類型中的類(靜態)變量一樣也存儲在方法區中。與Heap 同樣,method area是多線程共享的,所以要考慮多線程訪問的同步問題。好比,假設同時兩個線程都企圖訪問一個名爲Lava的類,而這個類尚未內裝載入虛擬機,那麼,這時應該只有一個線程去裝載它,而另外一個線程則只能等待。 (這裏可能出現的異常java.lang.OutOfMemoryError: PermGen full)
Java方法區與傳統語言中的編譯後代碼或是Unix進程中的正文段相似。它保存方法代碼(編譯後的java代碼)和符號表。在當前的Java實現中,方法代碼不包括在垃圾回收堆中,但計劃在未來的版本中實現。每一個類文件包含了一個Java類或一個Java界面的編譯後的代碼。能夠說類文件是Java語言的執行代碼文件。爲了保證類文件的平臺無關性,Java虛擬機規範中對類文件的格式也做了詳細的說明。其具體細節請參考Sun公司的Java 虛擬機規範。
Java stack
Java stack以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java stack執行兩種操做:以幀爲單位的壓棧或出棧。每當線程調用一個方法的時候,就對當前狀態做爲一個幀保存到java stack中(壓棧);當一個方法調用返回時,從java stack彈出一個幀(出棧)。
Java棧是與每個線程關聯的,JVM在建立每個線程的時候,會分配必定的棧空間給線程。它主要用來存儲線程執行過程當中的局部變量,方法的返回值,以及方法調用上下文。棧空間隨着線程的終止而釋放。
棧的大小是有必定的限制,若是在線程執行的過程當中,棧空間不夠用,那麼JVM就會拋出StackOverFlow異常,這種狀況通常是死遞歸形成的。下面的程序能夠說明這個問題。
public class TestStackOverFlow { public static void main( String[] args ) { Recursive r = new Recursive (); r.doit( 10000 ); // Exception in thread "main" java.lang.StackOverflowError } } class Recursive { public int doit( int t ) { if( t <= 1 ) { return 1; } return t + doit( t - 1 ); } }
Java虛擬機的棧有三個區域: 局部變量區、運行環境區、操做數區。
局部變量區:每一個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數和雙精度浮點數佔據了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如, 一個具備索引n的局部變量, 若是是一個雙精度浮點數, 那麼它實際佔據了索引n和n+1所表明的存儲空間) 虛擬機規範並不要求在局部變量中的64位的值是64位對齊的。虛擬機提供了把局部變量中的值裝載到操做數棧的指令,也提供了把操做數棧中的值寫入局部變量的指令。
運行環境區:在運行環境中包含的信息用於動態連接,正常的方法返回以及異常捕捉。
動態連接:運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用於支持方法代碼的動態連接。方法的class文件代碼在引用要調用的方法和要訪問的變量時使用符號。動態連接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋尚未定義的符號,並把變量訪問翻譯成與這些變量運行時的存儲結構相應的偏移地址。動態連接方法和變量使得方法中使用的其它類的變化不會影響到本程序的代碼。
正常的方法返回:若是當前方法正常地結束了,在執行了一條具備正確類型的返回指令時,調用的方法會獲得一個返回值。執行環境在正常返回的狀況下用於恢復調用者的寄存器,並把調用者的程序計數器增長一個恰當的數值,以跳過已執行過的方法調用指令,而後在調用者的執行環境中繼續執行下去。
異常捕捉:異常狀況在Java中被稱做Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的緣由是:①動態連接錯,如沒法找到所需的class文件。②運行時錯,如對一個空指針的引用。程序使用了throw語句。
當異常發生時,Java虛擬機採起以下措施:
操做數棧區:機器指令只從操做數棧中取操做數,對它們進行操做,並把結果返回到棧中。選擇棧結構的緣由是:在只有少許寄存器或非通用寄存器的機器(如 Intel486)上,也可以高效地模擬虛擬機的行爲。操做數棧是32位的。它用於給方法傳遞參數,並從方法接收結果,也用於支持操做的參數,並保存操做的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操做數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數將從堆棧彈出、相加,並把結果壓回到操做數棧中。
每一個原始數據類型都有專門的指令對它們進行必須的操做。每一個操做數在棧中須要一個存儲位置,除了long和double型,它們須要兩個位置。操做數只能被適用於其類型的操做符所操做。例如,壓入兩個int類型的數,若是把它們看成是一個long類型的數則是非法的。在Sun的虛擬機實現中,這個限制由字節碼驗證器強制實行。可是,有少數操做(操做符dupe和swap),用於對運行時數據區進行操做時是不考慮類型的。
Program counter
Java虛擬機的寄存器用於保存機器的運行狀態, 與微處理器中的某些專用寄存器相似。Java虛擬機的寄存器有四種:
在上述體系結構圖中,咱們所說的是第一種,即程序計數器,每一個線程一旦被建立就擁有了本身的pc寄存器。當線程執行Java方法的時候,它包含該線程正在被執行的指令的地址。可是若線程執行的是一個本地的方法,那麼程序計數器的值就不會被定義。
PC寄存器的內容老是指向下一條將被執行指令的地址,這裏的地址能夠是一個本地指針,也能夠是在方法區中相對應於該方法起始指令的偏移量。
Native method stack
對於一個運行中的Java程序而言,它還可能會用到一些跟本地方法相關的數據區。當某個線程調用一個本地方法時,它就再也不受到虛擬機關於結構和安全限制方面的約束,它既能夠訪問虛擬機的運行期數據區,也可使用本地處理器以及任何類型的棧。例如,本地棧是一個C語言的棧,那麼當C程序調用C函數時,函數的參數以某種順序被壓入棧,結果則返回給調用函數。在實現Java虛擬機時,本地方法接口使用的是C語言的模型棧,那麼它的本地方法棧的調度與使用則徹底與C語言的棧相同。
(這裏出現JVM沒法控制的內存溢出問題native heap OutOfMemory)