《深入理解java虛擬機》學習筆記——虛擬機字節碼執行

Java虛擬機規範中制定了虛擬機字節碼執行引擎的概念模型,使各種不同的虛擬機實現具有相同的行爲,即輸入的是字節碼文件,處理過程是字節碼解析,輸出的是執行結果。

運行時棧幀結構

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,存儲了方法的局部變量表、操作數棧、動態連接、方法返回地址等信息,每一個方法從調用開始至執行完成的過程,都會對應一個棧幀在虛擬機棧從入棧到出棧的過程。在編譯程序代碼的時候棧幀中需要多大的局部變量表、操作數棧都已完全確定,並且寫入了方法表的屬性中,即一個棧幀的大小不會受到運行期間變量數據的影響,僅取決於虛擬機的實現。

局部變量表

局部變量表用於存放方法參數和方法內部定義的局部變量,局部變量表的容量以變量槽--Slot爲最小單位,Slot的大小虛擬機規範沒有規定,但要求一個Slot能存放基本數據類型和引用類型。對於引用類型,需要滿足兩點:1、通過此引用直接或間接找到對象在Java堆中的起始地址索引;2、直接或間接找到對象類型在方法區中的類型信息。

方法執行時,虛擬機使用局部變量表完成從參數值到參數變量列表的傳遞過程,如果執行的是實例方法(非 static),局部變量表中第0位索引的Slot默認傳遞方法所屬對象實例的引用,也就是this。其餘參數按照參數表順序排列,之後分配方法體內部的局部變量。

操作數棧

當一個方法開始執行的時候,它的操作數棧是空的,之後執行中會有各種字節碼指令往操作數棧中寫入和提取內容。

操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,這個在編譯階段就會被嚴格檢查。

動態連接

每個棧幀都有一個指向運行時常量池中該棧幀所屬方法的引用,這麼做是爲了支持方法調用過程中的動態連接,也就是多態。

Class文件的常量池中存放了大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用作爲參數,這些符號引用一部分在類加載階段就轉化爲直接引用,稱爲靜態解析;另一部分在每一次運行期間轉化爲直接引用,稱爲動態連接。

方法調用

Java中方法的調用分爲解析調用和分派調用。在Java中符合“編譯器可知、運行期不可變”要求的方法主要有兩類,靜態方法和私有方法,這樣就決定了他們不會有多態的特性,因此在類加載階段就會被解析,也就是解析調用。另一類就是在運行時確定調用的具體是哪一個方法,分派調用。

與之對應的是5條字節碼指令

1、invokestatic 調用靜態方法

2、invokespecial 調用實例構造器<init>方法、私有方法和父類方法

3、invokevirtual 調用虛方法

4、invokeinterface 調用接口方法,在運行時確定具體的對象

5、invokedynamic 先在運行時動態解析出調用點限定符所引用的方法,然後再執行

解析調用是個靜態過程,編譯器確定;分配調用可能是靜態或動態,還可分單分派和多分派;

虛擬機(編譯器)在重載時是通過參數的靜態類型而不是實際類型作爲判定依據的,靜態類型編譯器可知。所有依賴靜態類型來定位方法執行版本的分派動作稱爲靜態分派,典型應用就是方法重載。下面是一個例子

public class Main {
	
	static abstract class Human {
	}
	
	static class Man extends Human {
	} 
	
	static class Woman extends Human {
	}
	
	public void sayHello(Human guy) {
		System.out.println("Hello, guy!");
	}
	
	public void sayHello(Man guy) {
		System.out.println("Hello, man!");
	}
	
	public void sayHello(Woman guy) {
		System.out.println("Hello, woman!");
	}

	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
//		Man man = new Man();
//		Woman woman = new Woman();
		Main main = new Main();
		main.sayHello(man);
		main.sayHello(woman);
	}
	
}

如果使用

Human man = new Man(); Human woman = new Woman();

這兩行,輸出結果就會是

Hello,guy! Hello,guy!

如果是Man man = new Man(); Woman woman = new Woman();

輸出就是Hello, man! Hello, woman!

靜態類型就是變量前的類型,實際類型是new 後面的類型,也就是對Human man = new Man()來說,man的靜態類型是Human,實際類型是Man,所以根據參數的靜態類型,選擇對應的方法輸出結果。

動態分派主要針對方法的重寫,也就是子類重寫父類的方法。

Java語言屬於靜態多分派、動態單分派類型。編譯期間,也就是靜態分派過程,依據靜態類型和參數類型選擇,判斷條件多於一個,屬於多分派;運行期間,就是動態分派過程,由於靜態分派時已經確定了方法的參數,這裏只需要確定變量的實際類型,來找到對應的重寫方法,也就是只需要一個判斷條件,所以屬於單分派。


發佈了54 篇原創文章 · 獲贊 12 · 訪問量 72萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章