執行引擎是Java虛擬機最核心的組成部分之一。虛擬機是一個相對於物理機的概念,這兩種機器都有代碼執行能力,其區別是物理機的執行引擎是直接創建在處理器、硬件、指令集和操做系統層面上的,而虛擬機的執行引擎則是由本身實現的,所以能夠自行指定指令集與執行引擎的結構體系,而且可以執行那些不被硬件直接支持的指令集格式。安全
在Java虛擬機規範中制定了虛擬機字節碼執行引擎的概念模型,這個概念模型成爲各類虛擬機執行引擎的統一外觀。從外觀上看起來全部Java虛擬機執行引擎都是一致的:輸入的是字節碼文件,處理過程是字節碼解析的等效過程,輸出的是執行結果。數據結構
棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,他是虛擬機運行時數據區中的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。每個方法從調用開始到執行完成的過程,就對應着一個棧幀在虛擬機裏面從入棧到出棧的過程。佈局
每個棧幀都包含了局部變量表、操做數棧、動態連接、方法返回地址和一些額外的附加信息。在編譯程序代碼的時候,棧幀中須要多大的局部變量表、多深的操做數棧都已經徹底肯定了,而且寫入到方法表的code屬性之中,所以一個棧幀須要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。this
一個線程中的方法調用鏈可能會很長,不少方法都同時處於執行狀態。對於執行引擎來說,活動線程中只有棧頂頂棧幀是有效的,成爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法。執行引擎所運行的字節碼指令都只針對當前棧幀進行操做。操作系統
局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。局部變量表以變量槽爲最小單位。虛擬機規範中並無明確指明一個slot應占用的空間大小。reference是對象的引用,虛擬機規範沒有說明他的長度,也沒有明確指出這個引用應有怎樣的結構,但通常來講,虛擬機實現至少都應當能今後引用中直接或間接的查找到對象在Java堆中的起始地址索引和方法區中的對象類型數據。對於64位的數據類型,虛擬機會以高位在前的方式爲其分配兩個連續的slot空間。因爲局部變量表創建在線程的堆棧上,是線程私有的數據,不管讀寫兩個連續的slot是不是原子操做,都不會引發數據安全問題。線程
在方法執行時,虛擬機使用局部變量表來完成參數值到參數變量列表的傳遞過程,若是是實例方法(非static方法),那麼局部變量表中第0位索引的slot默認是用於傳遞方所屬對象實例的引用,在方法中能夠經過關鍵字「this」來訪問這個隱含的參數。code
操做數棧也常被稱爲操做棧,他是一個後入先出棧。同局部變量表同樣,操做數棧的最大深度也是在編譯的時候被寫入到code屬性max_stacks數據項之中。操做數棧的每個元素能夠是任意的Java數據類型,包括long和double.32位數據類型所佔的棧容量爲1,64位數據類型所佔的棧容量爲2.在方法執行的任什麼時候候,操做數棧的深度都不會超過在max_stacks數據項中設定的最大值。對象
當一個方法開始執行的時候,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令指向操做數棧中寫入和讀取內容,也就是入棧出棧操做。例如,在作算數運算的時候是經過操做數棧來進行的,又或者在調用其餘方法的時候是經過操做數棧來進行參數傳遞的。索引
Java虛擬機的解釋執行引擎稱爲「基於棧的執行引擎」,其中所指的棧就是操做數棧。事件
每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。咱們知道Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化爲直接引用,這種轉化成爲靜態解析。另一部分將在每一次的運行期間轉化爲直接引用,這部分紅爲動態鏈接。
當一個方法執行後,有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者,是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法的方式稱爲正常完成出口。
另一種退出方式是在方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得處理。不管是Java虛擬機內部產生的異常仍是代碼使用athrow字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方式稱爲異常完成出口。一個方法使用異常出口是不會給他的上層調用者產生任何返回值的。
方法調用並不等同於方法執行,方法調用階段惟一的任務就是肯定被調用方法的版本(即調用哪個方法),暫時還不涉及方法內部的具體執行過程。在程序運行時,進行方法調用是最廣泛最頻繁的操做,Class文件在編譯過程當中不包含傳統編譯的鏈接步驟,一切方法調用在Class文件裏面存儲的都只是符號引用,而不是方法在實際運行時內存佈局中的入口地址(至關於以前所說的直接引用)。這個特性給Java帶來了更強大的動態擴展能力,但也使得Java方法的調用過程變得相對複雜起來,須要在類加載期間甚至到運行期間才能肯定目標方法的直接引用。
全部方法的調用中的目標方法在Class文件裏面都是一個常量池中的符號引用,在類加載的解析階段,會將其中的一部分符號引用轉化爲直接引用。這種解析能成立的前提是:方法在程序運行以前就有一個可肯定的調用版本,而且這個方法的調用版本在運行期是不改變的。換句話說,調用程序在程序代碼寫好,編譯器進行編譯時就必須肯定下來。這類方法的調用稱爲解析。
解析調用必定是一個靜態的過程,在編譯期間就徹底肯定,在類裝載的解析階段就會把涉及的符號引用所有轉變爲可肯定的直接引用,不會延遲到運行期再去完成。而分派調用則多是靜態的也多是動態的,根據分派依據的宗量數可分爲單分派和多分派。這兩類分派方式兩兩組合就構成了靜態單分派、靜態多分派、動態單分派、動態多分派四種分派狀況。
分派調用過程會揭示多態性特徵的一些最基本的體現(如重載和重寫)。
靜態分派:全部依賴靜態類型來定位方法執行版本的分派動做都稱爲靜態分派。靜態分派的最典型應用就是方法重載。靜態分派發生在編譯階段,所以肯定靜態分派的動做實際上不是由虛擬機來執行的。另外,編譯器雖然能肯定出方法的重載版本,但在不少狀況下這個重載版本並非惟一的,每每只能肯定一個更加適合的版本。這種模糊的結論在由0和1構成的計算機世界中是個比較「稀罕」的事件,產生這種模糊結論的主要緣由是字面量不須要定義,因此字面量沒有顯式的靜態類型,他的靜態類型只能經過語言上的規則去理解和推斷。
動態分派與「重寫」由很大關聯。