1. 程式人生 > >虛擬機器位元組碼執行引擎(幀棧執行結構、方法呼叫分派)

虛擬機器位元組碼執行引擎(幀棧執行結構、方法呼叫分派)

執行時幀棧結構

幀棧是在虛擬機器棧中的棧元素,每個幀棧包含區域性變量表、運算元棧、動態連線、方法返回地址和一些額外資訊。編譯時,幀棧需要多大區域性變量表,運算元棧多深都已確定,且分配了記憶體,不會受到執行期變數資料的影響。對執行引擎來說,活動執行緒中只有棧頂的棧幀是有效的,表示當前棧幀,其所關聯的方法為當前方法。

l  區域性變量表

1.   最小單位為Slot(一個32位以內的資料型別),有boolean,byte,char,short,int,float,reference,returnAddress這8種,reference為引用型別,returnAddress為一條位元組碼指令的地址。

對64位資料型別,高位在前的方式分配兩個Slot空間,如long、double(有非原子性協定,但由於幀棧為執行緒私有,不會發生併發安全問題)

2.   區域性變量回收

不回收:


仍不回收:


回收了!


因為placeholder即時作為區域性變數,在作用域之外該區域性變量表的Slot中仍會有對其的引用,直到有其他變數將其佔用的Slot複用才會使placeholder無法被GC Roots到達,真正使placeholder被回收。

3.   區域性變數必須賦初值才能使用

類變數在“準備”階段就有初始值,而後在初始化時又會賦予程式定義的初始值。而區域性變數沒有“準備”階段,因而區域性變數必須賦初值後才能使用:


不過該問題會在編譯期被檢查出來。

l  運算元棧

存放指令中的操作。每次指令操作時向運算元棧入棧和出棧的過程。另外棧幀之間存在內容共享:如區域性變量表共享和運算元棧共享。

l  動態連線

Class檔案中有大量符號引用,位元組碼中的方法呼叫指令就以常量池中指向方法的符號引用為引數。這些符號引用一部分在類的載入過程或第一次使用時直接引用(靜態解析);另外一部分在每次執行期間轉化為直接引用,這部分稱為動態連線。

l  方法返回地址

方法退出後用於返回方法被呼叫的位置。

方法正常退出時:呼叫PC計數器的值就可返回地址,棧幀中很可能儲存該計數值;

方法異常退出:返回地址通過異常處理器表來確定,棧幀中一般不會儲存該值。

方法呼叫

方法呼叫不是方法執行,方法呼叫階段唯一任務就是確定被呼叫方法的版本。

包括解析、分派(靜態分派與動態分派)

l  靜態分派

栗子:


輸出:


虛擬機器在過載時是通過引數的靜態型別而不是實際型別作為判定依據。且靜態型別是編譯期可知,實際型別是執行期可知,所以過載的方法版本是sayHello(Human)。所有依賴靜態型別來定位方法執行版本的分派動作,稱為靜態分派。


動態分派

多型的體現,多型都懂的,父類物件被賦予了子類物件,呼叫父類中定義的方法時實際會去呼叫子類中Override(覆蓋)的方法。


單分派與多分派

栗子:


結果:


結果毫無疑問,多分派是指:看看編譯期選擇過程,即靜態分派過程,選擇目標方法時的依據有兩點:一是靜態型別是Father還是Son,二是方法引數是QQ還是360。這次選擇產生了兩條invokevirtual指令,兩條指令引數分別為常量池中指向Father.hardChoice(_360)和Father.hardChoice(QQ)方法的符號引用,因此是根據兩個宗量選擇的,所以Java的靜態分派屬於多分派型別

單分派:在執行son.hardChoice(QQ)時, 由於編譯期已決定目標方法是hardChoice(QQ),所以執行期只關心接收者的實際型別是Son還是Father。因此只有一個宗量,所以Java的動態分派屬於單分派型別。