執行引擎是java虛擬機最核心的組成部分之一。在java虛擬機中制定了虛擬機字節碼執行引擎的概念模型,這個概念模型成了各類虛擬機執行引擎的統一外觀。在不一樣的虛擬機實現裏面,執行引擎可能會有解釋執行和編譯執行兩種選擇,也可能二者兼備,甚至還可能會包含幾個不一樣級別的編譯器執行引擎,可是從外觀上看起來,全部的java虛擬機的執行引擎都是一致的:輸入的是字節碼文件,處理過程是字節碼解析的等效過程,輸出的是執行結果。java
運行時棧幀結構數組
棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。每個棧幀都包括了局部變量表、操做數棧、動態鏈接、方法返回地址和一些額外的附加信息。編譯代碼的時候,須要多大的局部變量表,多深的操做數棧都已經徹底肯定了,而且寫入了方法表的Code屬性當中。對於執行引擎來講,在活動線程中,只有位於棧頂的棧幀纔是有效的,稱爲當前棧幀,與這個棧幀相關聯的稱爲當前方法。執行引擎運行的全部字節碼指令都只針對當前棧幀進行操做。
數據結構
概念模型:優化
1.局部變量表spa
局部變量表是一組變量存儲空間,用於存放方法參數和方法內定義的局部變量。以變量槽位最小單位(虛擬機規範中並無明確指明一個變量槽到底多大,只是說每一個變量槽都能存放一個boolean byte char short int float reference returnAddress類型的數據),這些都是32的長度的內存空間,因此變量槽會隨狀況而自動調整,對於64位的數據類型,虛擬機會以高位對齊的方式爲其分配兩個連續的內存槽空間。64位的數據類型只有long和double兩種,這裏把long和double數據類型讀寫分割爲兩次32位讀寫的作法與「long和double的非原子性協定」中把一次long和double數據類型讀寫分割爲兩次32位讀寫的作法有些相似。線程
虛擬機經過索引定位的方式使用局部變量表,索引值的範圍從0開始至局部變量表最大變量槽數量。若是是訪問的是32位的數據,那麼索引n就表明使用第n個變量槽,若是訪問的是64位,那麼就說明的是使用了n和n+1兩個變量槽。對於兩個相鄰變量槽儲存的64位數據,不容許使用任何方式單獨訪問其中的某一個。設計
方法執行時,虛擬機是使用局部變量表完成參數值到參數變量列表的傳遞過程的,若是執行的是實例方法,那局部變量表地0位索引變量槽默認是用於傳遞方法所屬對象的實例引用,其與參數則按照參數表順序排列,佔用從1開始的局部變量變量槽,參數表分配完畢後,再根據方法體內部定義的變量順序和做用域分配其與的變量槽。code
爲了儘量節省棧幀空間,局部變量表中的變量槽是能夠重用的,方法體中定義的變量,其做用域不必定會覆蓋整個方法體,若是當前字節碼PC結束期的值已經超出了某個變量的做用域,那這個變量對應的變量槽就能夠交給其餘變量使用,不過這樣的設計會帶來一些額外的反作用。對象
例如:變量的複用可能會直接影響系統的垃圾收集行爲blog
public static void main(String[] args){ byte[] placeholder=new byte[64*1024*1024]; System.gc(); }
這時候沒有回收,由於placeholder對象還處於做用域以內。
public static void main(String[] args){ { byte[] placeholder=new byte[64*1024*1024]; } System.gc(); }
加了花括號,placeholder對象的做用域被限制在了花括號以內,從代碼邏輯上講,在執行System.gc()的時候,placeholder對象已經不可能再被訪問了,可是發現仍是沒有被回收,再改一下
public static void main(String[] args){ { byte[] placeholder=new byte[64*1024*1024]; } int a=0; System.gc(); }
在調用System.gc()方法以前加一行int a=0,雖然很莫名其妙,可是對象被回收了。
在這三段代碼中,placeholder對象可否被回收的根本緣由是:局部變量表中的變量槽中是否還存有關於placeholder數組對象的引用。第一次修改中,雖然代碼已經離開了placeholder的做用域,可是以後,沒有任何對局部變量表的讀寫操做,placeholder本來所佔用的變量槽尚未被其餘變量複用,因此做爲GC Roots一部分的局部變量表仍然保持着對它的關聯,這種關聯沒被及時打斷。能夠選擇將不使用的對象手動賦值爲null,來代替int a=0;
還有一點是局部變量沒有類變量那樣的「準備階段」所以必須賦初值。
2.操做數棧
操做數棧能夠聽任何類型的java數據,32位數據類型所佔的棧容量爲1,64位爲2,在任什麼時候候,操做數棧的深度都不會超過code表中設置的最大值。
操做數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,在編譯程序代碼的時候,編譯器要嚴格保證這一點,在類校驗階段數據流分析中還要再次驗證這一點。
另外,在概念模型中,兩個棧幀做爲虛擬機棧的元素,是徹底互相獨立的。不少虛擬機裏會作一點優化,讓下面棧幀的部分操做數棧和上面棧幀的部分局部變量表重疊在一塊兒,這樣在進行方法調用時就能夠共用一部分數據
java虛擬機的解釋執行引擎稱爲「基於棧的執行引擎」,其中的棧指的就是操做數棧。
3.動態鏈接
每一個棧幀都包含了一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。
常量池中有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用做爲參數,這些符號引用一部分會在類加載階段或者第一次使用的時候就轉化爲直接引用,這種轉化成爲靜態解析;另一部分將在每一次運行期間轉化爲直接引用,這部分就稱爲動態鏈接
4.方法返回地址
一個方法運行之後,有兩種方式能夠退出這個方法:
①執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者,是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法稱爲正常完成出口
②方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得處理,不管是虛擬機內部的異常,仍是代碼中使用athrow字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方式叫異常完成出口
不管哪一種退出方式,方法退出以後,都須要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。通常來講,方法正常退出時,程序計數器的值能夠做爲返回地址,棧幀中極可能會保存這個計數器值。而方法異常退出時,返回地址是要經過異常處理器表來肯定的,棧幀中通常不會保存這部分信息。
5.附加信息
虛擬機規範容許具體的虛擬機實現增長一些規範裏沒有描述的信息到棧幀之中。
通常會把動態鏈接、方法返回地址與其餘附加信息稱爲棧幀信息