標籤:oid 結果 匹配 nal int 因此 override some 改變
Java虛擬機器--虛擬機器位元組碼執行引擎
所有的Java虛擬機器的執行引擎都是一致的:輸入的是位元組碼檔案,處理過程是位元組碼解析的等效過程,輸出的是執行結果。
運行時棧幀結構
用於支援虛擬機器進行方法調用和方法執行的資料結構,是虛擬機器棧的棧元素。每一個方法從調用開始到執行完成的過程,都對應一個棧幀在虛擬機器棧中的入棧出棧過程。
由於虛擬機器棧是線程私人的,所以每一個線程都有一個自己的虛擬機器棧,而每個虛擬機器棧都是由許多棧幀組成。每一個棧幀都包括
- 局部變數表
- 運算元棧
- 動態串連
- 方法返回地址
- 額外附加資訊
處於棧頂的稱為當前棧幀,對於執行引擎,在活動線程中只有當前棧幀是有效,與當前棧幀關聯的方法稱為當前方法。
局部變數表
用於存放方法參數和方法內定義的局部變數。虛擬機器通過索引定位的方式使用局部變數表,局部變數表的容量以變數槽(Variable Slot)為最小單位。局部變數不像類變數那樣有“準備階段”,不會被賦予系統初始值,所以在定義局部變數時一定要對其賦值。
運算元棧
又被稱為操作棧,當一個方法開始執行時,操作棧是空的,隨著方法的執行,各種位元組碼指令往運算元棧中寫入和提取內容,也就是出棧和入棧的操作。如在進行算術運算時就是通過運算元棧來進行的。
動態串連
每個棧幀都包括一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法調用過程中的動態串連。Class檔案的常量池中有大量的符號引用,位元組碼中的方法調用指令就以常量池中指向方法的符號引用作為參數。這些符號引用一部分會在類載入階段或第一次使用時就轉換為直接引用,這種轉化稱靜態解析;另外一部分在每一次運行期間轉化為直接引用,稱為動態串連。
方法返回地址
方法開始執行後,只有兩種方法可退出該方法:
- 正常完成出口:執行引擎遇到任意一個方法返回的位元組碼指令;
- 異常完成出口:執行過程中遇到異常,在本方法中沒有搜尋到匹配的異常處理器而導致的退出。
方法退出的過程實際上等同於將當前棧幀出棧,退出時可能執行的操作有:恢複上層方法的局部變數表和操作舒展,如有傳回值,把傳回值壓入調用者棧幀的運算元棧中,調用PC計數值以指向方法調用指令後面的一條指令。
方法調用
方法調用不同於方法執行,方法調用只是確定要調用哪一個方法,還不涉及方法內部的具體運行過程。所有方法調用在Class檔案中都是一個常量池的符號引用,在類載入甚至是運行期間才能確定目標方法的直接引用。在類載入的解析階段,會將一部分的符號引用轉化成直接引用(靜態解析),這種解析能成立的前提:
- 方法在程式真正運行之前就有一個可確定的調用版本
- 且這個方法在運行期間不可改變
滿足上述條件的方法主要有兩大類
- 靜態方法,直接與類型關聯
- 私人方法,在外部不能被訪問
這兩種方法各自的特點決定了它們不能通過繼承或者別的方式重寫其他版本,因此它們適合在類載入階段進行解析。
Java虛擬機器提供了5條方法調用位元組碼指令
- invokestatic:調用靜態方法
- invokespecial:調用執行個體構造器
<init>方法、私人方法和父類方法;
- invokevirtual:調用所有的虛方法
- invokeinterface:調用介面方法,會在運行時再確定一個實現此介面的對象;
- invokedynamic:先在運行時動態解析出調用點限定符所引用的方法,然後再執行該方法。
只要能被invokestatic、invokespecial指令調用的方法,都可以在解析階段確定唯一的調用版本,符合這個條件的有靜態方法、私人方法、執行個體構造器父類方法,它們可以在類載入時就把符號引用解析為該方法的直接引用。這些方法稱為非虛方法,除開final方法外的其他方法都稱為虛方法。
指派
指派調用可能是靜態也可能是動態。
靜態指派
靜態指派:所有以來靜態類型來定位方法執行版本的指派動作稱為靜態分配。靜態分配和重載的關係密切。
什麼是靜態類型,舉個例子,比如類Human、Man和Woman,其中Man和Woman繼承了Human。
package exercise;public class StaticDispatch { static class Human {} static class Man extends Human{} static class Woman extends Human{} public void someMethod(Human human) { System.out.println("Human"); } public void someMethod(Man man) { System.out.println("Man"); } public void someMethod(Woman woman) { System.out.println("Woman"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch s = new StaticDispatch(); s.someMethod(man); s.someMethod(woman); }}
上述main方法中,稱Human為靜態類型,或者外觀類型,而Man或者Woman被稱為實際類型。
靜態類型的變化僅僅在使用時發生,變數本身的靜態類型不會被改變,並且最終的靜態類型在編譯期時可知的;實際類型變化的結果在運行期才可確定,編譯器在編譯器的時候並不知道一個對象的實際類型是什麼。
// 實際類型變化Human man = new Man();man = new Woman();// 靜態類型變化s.someMethod((Man) man);s.someMethod((Woman) man);
編譯器在重載時是通過參數的靜態靜態類型而不是實際類型作為判斷依據的。因此上面的例子中會列印兩個"Human"而不是一個列印"Man"一個列印"Woman"。
如果在main中改為
s.someMethod((Man)man);s.someMethod((Woman) woman);
將會分別列印"Man"和"Woman"。
動態指派
動態分配:在運行期間根據實際類型確定方法的執行版本的分配過程。動態分配和多態中的重寫(Override)有密切的關聯。
舉個例子
package exercise;public class DynamicDispatch { static abstract class Human { protected abstract void someMethod(); } static class Man extends Human { @Override protected void someMethod() { System.out.println("Man"); } } static class Woman extends Human { @Override protected void someMethod() { System.out.println("Woman"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.someMethod(); woman.someMethod(); man = new Woman(); man.someMethod(); }}
上面的例子會列印
ManWomanWoman
單指派和多指派
方法的接收者和方法參數統稱為方法的宗量,根據指派基於多少種宗量,可以將指派分配劃分為多指派和單指派。
如果在指派過程中既要依據方法接收者而要依據方法參數,就是多指派,Java的靜態指派屬於多指派;如果在指派過程中只有某一種宗量作為選擇依據,其他宗量不會影響對虛擬機器的選擇,比如方法參數不影響虛擬機器選擇,唯一可以影響虛擬機器選擇的因素只有此方法的接收者,則是單指派,Java的動態指派屬於單指派。
總結一下:目前Java是一門靜態多指派、動態單指派的語言。
by @sunhaiyu
2018.6.16
Java虛擬機器--虛擬機器位元組碼執行引擎