每當啟動一個新線程時,Java虛擬機都會為它分配一個Java棧。Java棧以幀為單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操作:以幀為單位的壓棧和出棧。
某個線程正在執行的方法被稱為該線程的當前方法,當前方法使用的棧幀稱為當前幀,當前方法所屬的類稱為當前類,當前類的常量池稱為當前常量池。在線程執行一個方法時,它會跟蹤當前類和當前常量池。此外,當虛擬機遇到棧內操作指令時,它對當前幀內數據執行操作。
每當線程調用一個Java方法時,虛擬機都會在該線程的Java棧中壓入一個新幀。而這個新幀自然就成為了當前幀。在執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等數據。
Java方法可以以兩種方式完成。一種通過return返回的,稱為正常返回;一種是通過拋出異常而異常終止的。不管以哪種方式返回,虛擬機都會將當前幀彈出Java棧然后釋放掉,這樣上一個方法的幀就成為當前幀了。
Java幀上的所有數據都是此線程私有的。任何線程都不能訪問另一個線程的棧數據,因此我們不需要考慮多線程情況下棧數據的訪問同步問題。當一個線程調用一個方法時,方法的的局部變量保存在調用線程Java棧的幀中。只有一個線程能總是訪問那些局部變量,即調用方法的線程。
JVM棧之局部變量表:包含參數和局部變量
局部變量表存放了基本數據類型、對象引用和returnAddress類型(指向一條字節碼指令的地址)。其中64位長度的long和double類型的數據會占用2個局部變量空間(slot)(下圖1到3的原因),其余數據類型只占用1個。局部變量表所需的內存空間在編譯期間完成分配。每個方法都對應一個棧幀。
public class StackDemo { //靜態方法 public static int runStatic(int i, long l, float f, Object o, byte b) { return 0; } //實例方法 public int runInstance(char c, short s, boolean b) { return 0; } }
其對應的局部變量表如下:
上方表格中,靜態方法和實例方法對應的局部變量表基本類似。但有以下區別:實例方法的表中,第一個位置存放的是當前對象的引用。
JVM棧之操作數棧
Java沒有寄存器,所有參數傳遞都是使用操作數棧。
public static int add(int a,int b){ int c=0; c=a+b; return c; }
壓棧的步驟如下:
0: iconst_0 // 0壓棧
1: istore_2 // 彈出int,存放於局部變量2
2: iload_0 // 把局部變量0壓棧
3: iload_1 // 局部變量1壓棧
4: iadd //彈出2個變量,求和,結果壓棧
5: istore_2 //彈出結果,放於局部變量2
6: iload_2 //局部變量2壓棧
7: ireturn //返回
如果計算100+98的值,那么操作數棧的變化如下圖所示:
JVM棧之棧上分配(動態鏈連接)
小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
直接分配在棧上,可以自動回收,減輕GC壓力
大對象或者逃逸對象無法棧上分配