虛擬機字節碼執行引擎

虛擬機和物理機,兩者都有代碼執行能力,區別是物理機的執行引擎是直接創建在處理器、硬件、指令集和操做系統層面上,而虛擬機的執行引擎能夠本身實現,所以能夠自定義指令集和執行引擎的結構體系,而且可以執行那些不被硬件直接支持的指令集格式java

執行引擎是java虛擬機最爲核心的組成部分,不一樣虛擬機實現裏,執行引擎在執行java代碼的時候可能有解釋執行和編譯執行或者二者兼備。可是從外觀來看,全部的java虛擬機執行引擎都是一致的:輸入的是字節碼文件,處理過程是字節碼解析的等效過程,輸出的是執行結果數據結構


 運行時的棧幀結構架構

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,是虛擬機運行時數據區中的虛擬機棧的棧元素。佈局

棧幀存儲了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。spa

每個方法從調用開始到執行完成的過程,就對應一個棧幀在虛擬機棧裏從入棧到出棧的過程。操作系統

編譯程序代碼的時候,棧幀須要多大的局部變量表、多深的操做數棧都已經徹底肯定了,所以一個棧幀須要分配多少內存不會受到運行期變量數據的影響。線程

活動線程中只有棧頂的棧幀是有效的,稱爲當前棧幀,執行引擎所運行的全部字節碼指令都只針對當前棧幀進行操做。 code

1.局部變量表對象

局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。最小單位是slot稱爲變量槽。blog

public static void main(String[] args){
  {
       byte[] placeholder = new byte[64*1024*1024];
   }  
   System.gc();
}

===========

public static void main(String[] args){
  {
       byte[] placeholder = new byte[64*1024*1024];
   }  
   int a = 0;
   System.gc();
}

局部變量表中的slot是可重用的,若是超過某個變量的做用域,那麼這個變量對應的slot就能夠交給其餘變量使用。(不只節省棧空間還會影響到垃圾回收行爲)

第一種狀況,代碼雖然離開了placeholder的做用域,可是以後沒有任何對局部變量表的讀寫操做,placeholder本來佔用的slot尚未被其餘變量複用,因此做爲GC Roots一部分的局部變量表仍然保持着對它的關聯。  而第二種狀況就會複用原來的slot,此時會致使內存被回收。

 

局部變量不會有「準備階段」,即不會賦予它初始值,一個局部變量若是定義了可是沒有賦初始值是不能使用的。//   定義的時候不會報錯,使用的時候會提示未初始化

2.操做數棧

iadd指令運行時,要求操做數棧中最接近棧頂的兩個元素已經存入兩個int數值,執行這個指令時,會將這兩個int值出棧相加,而後將相加的結果入棧。

3.動態鏈接

每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用的過程當中的動態鏈接。

字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數,這些符號一部分會在類加載階段或第一次使用的時候轉化爲直接引用,這些轉化稱爲靜態解析。另外一部分將在每一次的運行期間轉化爲直接引用,這部分稱爲動態鏈接

4.方法返回地址

當一個方法被執行後有兩種方式退出這個方法,第一種方式是執行引擎遇到任意一個方法返回的字節碼指令。這種稱爲正常完成出口。

另外一種退出方式是,在方法執行過程遇到了異常,而且這個異常沒有在方法體內獲得處理。這種退出方法稱爲異常完成出口。

不管採用哪一種方法退出,方法退出後,都要返回到方法被調用的位置,程序才能繼續,方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。

方法退出的過程實際上等同於把棧幀出棧,可能執行的操做有:恢復上層方法的局部變量表和操做數棧把返回值壓入調用者棧幀的操做數棧中調整PC計數器的值以指向方法調用指令後面的一條指令等。


方法調用

Class文件的編譯過程當中不包含傳統編譯中的鏈接步驟,一切方法調用在Class文件裏面存儲的都只是符號引用,而不是方法在實際運行時內存佈局中的入口地址(至關於直接引用)這個特性爲java帶來了更強大的動態擴展能力。

1.解析

在類的解析階段,會將一部分符號引用轉化爲直接引用,這種解析成立的前提是:方法在程序真正運行以前就有一個可肯定的調用版本,而且這個方法的調用版本在運行期是不可變的。(靜態方法、私有方法、構造方法還有final修飾的方法,這幾類方法沒法被重寫稱之爲非虛方法

2.分派

Human man = new Man()  //human稱爲變量的靜態類型,man稱爲變量的實際類型,編譯器在編譯階段並不知道對象的實際類型。

編譯器在方法重載時,是經過參數的靜態類型而不是實際類型來做爲斷定依據的。

全部依賴靜態類型來定位方法執行版本的分派動做都稱爲靜態分派。典型應用就是方法重載。靜態分配發生在編譯階段

Java是一門靜態多分派(重載,方法參數和方法接收者,編譯階段)、動態單分派(重寫,方法的接收者,運行階段)的語言。


基於棧的指令集與基於寄存器的指令集

java編譯器輸出的指令流,基本上是一種基於棧的指令集架構,這些指令依賴操做數棧進行工做,優勢是可移植性,寄存器直接由硬件提供,程序若是直接依賴這些硬件寄存器則不可避免地要受到硬件的約束。缺點是因爲頻繁的出棧入棧,致使執行速度相對較慢。

x86二地址指令集,基於寄存器的指令集,這些指令依賴寄存器進行工做

相關文章
相關標籤/搜索