Java棧與棧上分配

weixin_34050427發表於2018-03-14

一. java棧:

java棧是一塊執行緒私有的記憶體空間。如果說,java堆和程式資料密切相關,那麼java棧就是和執行緒執行密切相關的。執行緒執行的基本行為是函式呼叫,每次函式呼叫的資料都是通過Java棧傳遞的。

java heap,java stack 與Javametaspace之間的關係:

5928323-8767a494111c3b37.png
00001.png

特點:

  • 執行緒私有
  • 棧由一系列幀組成(因此Java棧也叫做幀棧)
  • 幀儲存一個方法的區域性變數、運算元棧、常量池指標
  • 每一次方法呼叫建立一個幀,並壓棧

1.棧的結構和組成:

1)棧的結構:

  • 這是一塊先進後出的資料結構,只支援出棧和入棧兩種操作。在java棧中儲存的主要內容是棧幀。每一次函式呼叫都會有一個相應的棧幀入棧,每個函式呼叫結束,都有一個棧幀彈出java棧。當前正在執行的函式對應的棧就是當前的幀(位於棧頂)。
  • 每個棧幀中,至少包含區域性變數表,運算元棧和幀資料區幾個部分。
  • 注意由於每次函式呼叫都會生成棧幀並佔有一定的棧空間。因此如果棧空間不足,函式呼叫就無法進行下去。系統就會丟擲StackOverflowOver棧溢位的錯誤。例如遞迴時,會有很多棧幀入棧。jvm提供了-Xss來指定執行緒的最大棧空間,這個引數決定了函式呼叫的深度。

2)棧組成:

棧由棧幀組成,棧幀由區域性變數表,運算元棧,幀資料區組成。

  • 區域性變數表:
    用於儲存函式的引數(實參)變數和區域性變數。區域性變數表中的變數只在當前函式呼叫中有效,當函式呼叫結束後,隨著函式棧幀的銷燬,區域性變數表也會隨之銷燬。

  • 運算元棧:
    棧幀的一部分,也是個先入先出的資料結構。用於計算過程的中間結果,同時作為計算過程中變數臨時的儲存空間。

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 //返回

a,b變數的值分別是100和98,以下是運算元棧的工作原理以及和區域性變數表的關係:


5928323-184ee8d78fea65fb.jpeg
00002.jpeg
  • 幀資料區:
    棧幀需要資料開支援常量池解析,正常方法返回和異常處理等
    以下的例子是個遞迴,沒有遞迴的出口,會出現棧溢位,並列印遞迴的深度。
 public class TestStackDeep {
    private static int count=0;
    public static void recursion(long a,long b,long c){
        long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
        count++;
        System.out.println(count);
        recursion(a, b, c);
    }

    public static void recursion(){
        count++;
        System.out.println(count);

        recursion();
    }

    public static void main(String[] args) {
        try {
//          recursion(0L,0L,0L);
            recursion();
        }catch (Exception e){
            System.out.println("deep of calling="+count);
            e.fillInStackTrace();
        }
    }
}

影響棧空間使用的因素:
1.闡述列表的引數多。
2.遞迴的深度過深了。

  • -Xss256k:
    deep of calling=568
    遞迴呼叫了568次

  • -Xss512k:
    deep of calling=3030
    遞迴呼叫了568次

  • Exception in thread "main" java.lang.StackOverflowError
    棧溢位,棧的空間滿了。可以通過減少引數或區域性變數的個數,減少棧空間的佔用,達到函式多呼叫幾次的目的。

  • 呼叫recursion(a, b, c);-Xss256k:
    最大深度716

  • 呼叫呼叫recursion(),-Xss256k:
    最大深度1963

  • 可以看到在相同的棧容量下,區域性變數少的函式可以支援更深的函 數呼叫。

二.棧上分配:

棧上分配是jvm的一個優化技術,對於那些執行緒私有的物件,可以將它們分配在棧上,而不是堆上。棧上分配的好處是可以在函式呼叫後自行銷燬,而不是GC介入,從而提升了系統的效能。
棧上分配的基礎是逃逸分析,逃逸分析的目的是判斷物件的作用域是否有可能逃逸出函式體。
函式alloc()內的變數b是執行緒私有的區域性變數,

public class OnStackTest {
    public static void alloc(){
        byte[] b=new byte[2];
        b[0]=1;
    }

    public static void main(String[] args) {
        long b=System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            alloc();
        }
        long e=System.currentTimeMillis();
        System.out.println(e-b);
    }
}
  • 第一種執行方式:-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC

  • 這種方式new物件在棧上分配,gc不參與回收,因為變數僅僅在棧上分配空間,降低gc的工作量,同時防止堆上的空間被佔用
    輸出結果 5 效率很高。

  • 第二種執行方式:-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
    這種方式new物件在java堆上分配,gc參與釋放
    輸出結果:
    ……
    [GC 3550K->478K(10240K), 0.0000977 secs]
    [GC 3550K->478K(10240K), 0.0001361 secs]
    [GC 3550K->478K(10240K), 0.0000963 secs]
    564
    GC的效率明顯低於棧上分配對棧幀的銷燬的效率。

  • 小物件(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上

  • 直接分配在棧上,可以自動回收,減輕GC壓力
    大物件或者逃逸物件無法棧上分配

逃逸分析:
下面的程式碼顯示了一個逃逸的物件:因為程式碼中的User的作用域是整個Main Class,所以user物件是可以逃逸出函式體的。

public class PartionOnStack {
   static class User{
    private int id;
    private String name;
    public User(){}
       }
    private static  User user;//在這裡逃逸
    public static void foo() {
    user=new User();
    user.id=1;
    user.name="sixtrees";
    }
    public static void main(String[] args) {
    foo();
    }
}

下面的程式碼展示的則是一個不能逃逸的程式碼段。(不能逃逸的才能棧上分配)

public class PartionOnStack {
    class User{
    private int id;
    private String name;
    public User(){}
       }
    public  void foo() {
    User user=new User();
    user.id=1;
    user.name="sixtrees";
    }
    public static void main(String[] args) {
    PartionOnStack pos=new PartionOnStack();
    pos.foo();
    }
}

總結:
*小物件(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
*直接分配在棧上,可以自動回收,減輕GC壓力
*大物件或者逃逸物件無法棧上分配

相關文章