一、運行時棧結構
棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構。
棧幀包括:局部變量表、操作數棧、動態連接、方法的返回地址 和一些額外的附加信息。
執行引擎運行的所有字節碼指令只針對當前棧幀進行操作。需要注意的是一個棧中能容納的棧幀是受限,過深的方法調用可能會導致StackOverFlowError,當然,我們可以認爲設置棧的大小。其模型示意圖大體如下:
執行引擎的所有字節碼指令都只針對當前棧幀進行操作。
1、局部變量表
是變量值的存儲空間,由方法參數和方法內部定義的局部變量組成,其容量用Slot1作爲最小單位。
slot可以存放32位以內的數據類型。slot的長度可以隨處理器、操作系統或虛擬機不同而發生改變。
局部變量表的大小在編譯時就可以確定,Code中的max_local。
局部變量表不存在系統初始化的過程,這意味着一旦定義了局部變量則必須人爲的初始化,否則無法使用。
JVM通過索引定位方式使用局部變量表。
在方法執行時,如果是實例方法,那麼局部變量表的第0位索引的slot默認是用於傳遞方法所屬類實例的引用(也就是this)。
2、操作數棧
操作數棧的最大深度也在編譯的時候寫入到Code屬性的max_stacks數據項中。
另外我們說Java虛擬機的解釋引擎是基於棧的執行引擎,其中的棧指的就是操作數棧。
操作數棧的每一個元素可以是任意java類型。
作用:在做算術運算的時候通過操作數棧來進行又或者調用其他方法的時候通過操作數棧來進行參數傳遞。
兩個棧幀(局部變量表和操作數棧)是完全相互獨立的,jvm會做一些優化處理,令兩個棧幀出現一部分重疊。
3、動態連接
每一個棧幀中都包含一個指向運行時常量池中該棧所屬方法的符號引用,持有該引用是爲了支持方法調用過程中的動態連接(符號引用轉換成直接引用)。
4、方法返回地址
方法執行後有兩種方式可以退出這個方法:
(1)執行引擎遇到任意一個方法返回的字節碼指令,也就是所謂的正常完成出口。
(2)在方法執行的過程中遇到了異常,並且這個異常沒有在方法內進行處理,也就是只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種方式成爲異常完成出口。
無論通過哪種方式退出,在方法退出後都返回到該方法被調用的位置。這個就是存放返回位置的地址。
二、方法調用
方法調用不等同於方法執行,方法調用只是確定調用方法版本(哪個方法)。
1、解析(在類加載階段,將其中一部分符號引用轉換成直接引用)。
前提:方法在程序運行之前就可以確定調用的版本,並且運行期不可變。符合這個條件的有:靜態方法、私有方法、實例構造器、父類方法,這些方法都可以稱非虛方法(還有final),也就是不能多態。
2、分派
宗量:方法的接受者與方法的參數稱爲方法的宗量。
靜態分派:所有依賴靜態類型3來定位方法執行版本的分派成爲靜態分派,發生在編譯階段,典型應用是方法重載。
動態分派:在運行期間根據實際類型4來確定方法執行版本的分派成爲動態分派,發生在程序運行期間,典型的應用是方法的重寫。
單分派:根據一個宗量5 對目標方法進行選擇。動態派就是其中一種。
多分派:根據多於一個宗量對目標方法進行選擇。靜態分派時其中一種。
三、基於棧的字節碼執行引擎
public class MainTest {
public static int add(){
int result=0;
int i=2;
int j=3;
int c=5;
return result =(i+j)*c;
}
public static void main(String[] args) {
MainTest.add();
}
}