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