深入理解Java虛擬機系列——JVM的棧內存

    每當啓動一個新線程時,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壓力 ,大對象或者逃逸對象無法棧上分配。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章