运行时栈帧结构
棧幀是用于虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。每一個棧幀的如棧和出棧過程代表了一個方法執(zhí)行和結(jié)束的過程。
每一個棧幀包括局部變量表,操作數(shù)棧,動態(tài)鏈接,返回地址等等信息。棧幀的所有信息在編譯代碼階段就已經(jīng)確定了,不會收到運行時數(shù)據(jù)的影響。
?
?
1.局部變量表
每一個變量都會儲存在Slot中,64位機器的Slot占64位,每個Slot都可以存儲java數(shù)據(jù)類型和引用類型。
局部變量表的第0位默認是調(diào)用此方法的實例對象的引用,也就是this。其他參數(shù)按照順序,占用從1開始的Slot。
為了節(jié)省棧幀空間,局部變量中的Slot是可以重用的,因為作用域不一定覆蓋整個方法體。
對于局部變量表中變量的初始化,它不像類變量(static修飾)在類加載過程中的 “加載” 和 “初始化” 兩次初始化,而是如果沒初始化就不能使用。例如
class A {public static int a;public void haha {int b;System.out.printf(a);//1System.out.printf(b);//2} }//1編譯通過而//2編譯錯誤。
?
2.操作數(shù)棧
當一個方法開始執(zhí)行的時候,不同于局部變量表從一開始分配好內(nèi)存, 操作數(shù)棧一開始是空的,執(zhí)行過程中,會有很多字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是執(zhí)行入棧,出棧操作。例如加法操作就是通過操作數(shù)棧來進行的。
在概念模型中,兩個棧幀是互相獨立的,但是在大多數(shù)情況下都會做一些優(yōu)化。
可以看的出來,下面的棧幀的部分操作數(shù)棧與上面的局部表重疊在一起。這也很容易理解,例如:
class A {main方法{int a = 1;int b = 2;int c = add(a , b); //執(zhí)行到這里}public func int add(int a , int b){return a + b;} }main方法執(zhí)行到add(a,b)方法的時候,add方法棧幀在上層,main函數(shù)棧幀在下層,a,b在下層main函數(shù)的操作數(shù)棧中,也在當前執(zhí)行函數(shù)add方法中的局部變量中,這樣進行方法調(diào)用時就可以共用一部分數(shù)據(jù),無需進行額外的參數(shù)復制傳遞。
?
3.動態(tài)連接
每個棧幀中都包含一個指向運行時常量池中該棧幀中所屬方法的引用,持有這個引用是為了支持動態(tài)連接。
動態(tài)連接是什么?
Class常量池中有很多符號引用,一部分 符號引用 會在類加載過程中轉(zhuǎn)化成直接引用,叫做靜態(tài)解析,另一部分會在運行時轉(zhuǎn)化成直接引用,叫做動態(tài)引用。靜態(tài)解析的函數(shù)類型有靜態(tài)方法,構(gòu)造函數(shù),父類方法,私有方法等是“不可修改”的函數(shù)。
4.方法返回地址
退出方法有兩種形式,正常退出和異常退出。
正常退出的情況下,方法會返回字節(jié)碼命令(可能包含返回值)給調(diào)用者。而且返回地址是調(diào)用函數(shù)的PC計數(shù)器的值。
異常退出的情況下,是不會給調(diào)用者返回任何返回值的,而且,返回地址是要通過異常處理器來確定的。
因此,方法正常退出時,吧返回值壓入調(diào)用者棧幀的操作數(shù)棧中,然后調(diào)整PC計數(shù)器的值指向下一條命令。異常退出是恢復上層方法中的局部變量表和操作數(shù)棧。
總結(jié)