java虛擬機讀書筆記 第八章 虛擬機字節碼執行引擎

java虛擬機規範裏制定了虛擬機字節碼執行引擎的概念模型。在不一樣的虛擬機實現裏,執行引擎在執行java代碼的時候可能會有解釋執行和編譯執行。但從外觀上來看,全部java虛擬機的執行引擎是一致的:輸入的是字節碼文件,處理過程是字節碼解析的等效過程,輸出的是執行結果java

運行時棧幀結構

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。每個方法從調用開始至執行完成的過程都對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。安全

局部變量表

局部變量表是一組變量值存儲,用於存放方法參數和方法內部定義的局部變量。在Java程序編譯爲class文件時,就在code屬性的max_locals數據項中肯定了該方法所須要分配的局部變量表的最大容量。 局部變量表的容量以變量槽Slot爲最小單位,每一個Slot都應用能存放一個boolean、byte、char、short、int、float、reference或returnAddress類型的數據(java中佔用32位之內的8中類型)。對於64位的數據類型,會採用高位對齊的方式分配兩個連續的Slot空間。局部變量創建在線程的堆棧上是線程私有數據。連續寫入Slot不會引發線程安全。 虛擬機經過索引的方式訪問局部變量表,索引值的訪問從0開始。 在方法執行時,虛擬機是使用局部變量表完成參數值到參數列表的傳遞過程的,若是執行的是實例方法,局部變量表的第0位索引默認是傳遞方法所屬的實例對象,經過this來訪問。爲了節省空間,Slot是能夠複用的,但某些狀況下會影響到gc。 局部變量不像類變量存在準備階段,若是一個局部變量定義了可是沒有賦初始值是不能使用的。數據結構

操做數棧

它是後入先出(LIFO)棧。同局部變量,操做數棧的棧深度也在編譯的時候寫入到了Code屬性的max_stacks數據項中。操做數棧中的每個元素能夠是任意的java數據類型,包括long和double。在方法執行的任什麼時候候,操做數棧的深度都不能超出max_stack數據項中設定的最大值。 Java虛擬機的解釋執行引擎稱爲基於棧的執行引擎,其中所指的棧就是操做數棧。性能

動態鏈接

每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中動態鏈接。字節碼中方法的調用指令就以常量池中指向方法的符號引用做爲參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉換爲直接引用,這種轉化稱爲靜態解析。另外一部分將在每一次運行期間轉化爲直接引用,這部分稱爲動態鏈接。優化

##方法返回地址this

當一個方法開始執行後,只有兩種方式能夠退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這種退出方式稱爲方法的正常完成出口。另外一中退出方式是,在方法執行過程當中遇到了異常,而且這個異常沒有在方法體中正確的獲得處理,這種退出方法的方式稱爲異常完成出口,經過這種方式退出,不會給它的上層調用返回任何值。方法正常退出時,調用者的pc計數器的值能夠做爲返回地址。而方法異常退出時,返回地址是要經過異常處理器表來肯定的。 方法退出的過程實質上就是把當前棧幀出棧,所以退出時可能執行的操做有:恢復上層方法的局部變量表和操做數棧,把返回值壓入調用者操做數棧中,調整pc計數器的值以指向下一條指令。 通常會把動態鏈接、方法返回地址與其餘附加信息所有歸爲一類,稱爲棧幀信息。線程

方法調用

方法調用的階段的惟一任務就是肯定被調用方法的版本(即調用哪個方法)。code

解析

全部方法調用的目標在Class文件裏面都是一個常量池中的符號引用,在解析階段會把符號引用轉化爲直接引用,這種解析能成立的前提是:方法在程序真正能運行以前就有一個能夠肯定的調用版本,而且這個版本在方法的調用階段是不可改變的。這類方法的調用稱爲解析。對象

java中符合編譯期可知,運行期不可變的,主要包括:靜態方法和私有方法兩大類。前者於類型關聯,後者在外部沒法訪問,都不可能經過繼承或別的方式重寫其餘版本,適合在類加載階段進行解析。繼承

只要能被invokestatic和invokespecial指令調用的方法,均可以在解析階段中肯定惟一的調用版本,符合這個條件的有靜態方法、私有方法、實例構造器、父類方法,他們在類加載的時候就會把符號引用轉化爲直接引用。這些方法稱爲非虛方法,其餘方法稱爲虛方法(不包括final)。雖然final方法是使用invokevirtual指令調用的,可是它沒法被覆蓋。java語言規範中明確說明final方法是一種非虛方法。

分派

解析調用必定是個靜態的過程,在編譯期可知,在類加載的解析階段就會把涉及到的符號引用轉化爲直接引用,不會延遲到運行期再去完成。而分派調用則可能靜態的也多是動態的,根據分派依據的宗量數可分爲單分派和多分派,因此分派共有4種狀況:靜態單分派、靜態多分派、動態單分派、動態多分派。

靜態分派

全部依賴靜態類型來定位方法執行版本的分派動做稱爲靜態分派。靜態分派的典型應用是方法的重載。靜態分派發生在編譯階段,所以肯定靜態分派的動做實際上不是由虛擬機來執行的。

動態分派

它和多態性的重寫有着很密切的聯繫。重載是靜態的,重寫是動態的。

invokevirtual指令的運行時解析過程大體分爲如下幾個步驟: 1)找到操做數棧頂的第一個元素所指向的對象的實際類型,記做C。 2)若是在類型C中找到的與常量中的描述符和簡單名稱都相符合的方法,則進行訪問權限校驗,若是經過則返回這個方法的直接引用,查找結束;若是不經過,則返回java.lang.IllegalAccessError異常。 3)不然,按照繼承關係從下往上依次對C的各個父類進行第2步的搜索和驗證過程。 4)若是始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。

單分派與多分派

方法的接收者與方法的參數統稱爲方法的宗量。根據分派基於多少種宗量,能夠將分派劃分爲單分派和多分派兩種。單分派是根據一個宗量對目標方法進行選擇,多分派則是根據多個宗量對目標方法進行選擇。今天的java語言是一門靜態多分派、動態單分派的語言。

虛擬機動態分派的實現

因爲動態分派是很是頻繁的動做,並且動態分派的方法版本選擇過程須要運行時在類的方法元數據中搜索合適的目標方法。面對這種狀況,最經常使用的穩定優化手段就是爲類在方法區中創建一個虛方法表。使用虛方法表索引來代替元數據查找以提升性能。虛方法表中存放着各個方法的實際入口地址。

相關文章
相關標籤/搜索