1. 程式人生 > 實用技巧 >JVM 05 執行時資料區 JAVA虛擬機器棧

JVM 05 執行時資料區 JAVA虛擬機器棧

版權宣告:源出處:尚矽谷JVM

部落格來源於大佬整理

虛擬機器棧的背景

由於跨平臺性的設計,java的指令都是根據棧來設計的。不同平臺CPU架構不同,所以不能設計為基於暫存器的。
根據棧設計的優點是跨平臺,指令集小,編譯器容易實現,缺點是效能下降,實現同樣的功能需要更多的指令。

記憶體中的堆與棧

棧是執行時的單位,而堆是儲存的單位
1.棧解決程式的執行問題,即程式如何執行,或者說如何處理資料。堆解決的是資料儲存的問題,即資料怎麼放、放在哪兒。

2.一般來講,物件主要都是放在堆空間的,是執行時資料區比較大的一塊

3.棧空間存放 基本資料型別的區域性變數,以及引用資料型別的物件的引用

Java虛擬機器棧的特點

1.java虛擬機器棧(Java Virtual Machine Stack),早期也叫Java棧。 每個執行緒在建立時都會建立一個虛擬機器棧,其內部儲存一個個的棧幀(Stack Frame),對應這個一次次的java方法呼叫。它是執行緒私有的

2.生命週期和執行緒是一致的

3.棧是一種快速有效的分配儲存方式,訪問速度僅次於PC暫存器(程式計數器)

4.作用:主管java程式的執行,它儲存方法的區域性變數、8種基本資料型別、物件的引用地址、部分結果,並參與方法的呼叫和返回。

區域性變數:相較於成員變數(成員變數或稱屬性)

基本資料變數:8種基本資料型別

引用型別變數:類,陣列,介面

5.JVM直接對java棧的操作只有兩個

(1)每個方法執行,伴隨著進棧(入棧,壓棧)

(2)執行結束後的出棧工作

6.對於棧來說不存在垃圾回收問題,但是肯定存在OOM異常

下面接著說Java虛擬機器棧的異常

棧中可能出現的異常

java虛擬機器規範允許Java棧的大小是動態的或者是固定不變的

如果採用固定大小的Java虛擬機器棧,那每一個執行緒的java虛擬機器棧容量可以線上程建立的時候獨立選定。如果執行緒請求分配的棧容量超過java虛擬機器棧允許的最大容量,java虛擬機器將會丟擲一個 StackOverFlowError異常

/**
 * 演示棧中的異常
 */
public class StackErrorTest {
    public static void main(String[] args) {
        main(args);
    }
}

如果java虛擬機器棧可以動態拓展,並且在嘗試拓展的時候無法申請到足夠的記憶體,或者在建立新的執行緒時沒有足夠的記憶體去建立對應的虛擬機器棧,那java虛擬機器將會丟擲一個OutOfMemoryError異常

設定棧的記憶體大小

我們可以使用引數-Xss選項來設定執行緒的最大棧空間,棧的大小直接決定了函式呼叫的最大可達深度。 (IDEA設定方法:Run-EditConfigurations-VM options 填入指定棧的大小-Xss256k)

/**
 * 演示棧中的異常
 *
 * 預設情況下:count 10818
 * 設定棧的大小: -Xss256k count 1872
 */
public class StackErrorTest {
    private static int count = 1;
    public static void main(String[] args) {
        System.out.println(count);
        count++;
        main(args);
    }
}

Java虛擬機器棧的儲存結構和執行原理

1.每個執行緒都有自己的棧,棧中的資料都是以棧幀(Stack Frame)的格式存在

2.在這個執行緒上正在執行的每個方法都對應各自的一個棧幀

3.棧幀是一個記憶體區塊,是一個數據集,維繫著方法執行過程中的各種資料資訊

4.JVM直接對java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循先進後出/後進先出的和原則。

5.在一條活動執行緒中,一個時間點上,只會有一個活動的棧幀。即只有當前正在執行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為當前棧幀(Current Frame),與當前棧幀對應的方法就是當前方法(Current Frame)

6.執行引擎執行的所有位元組碼指令只針對當前棧幀進行操作

7.如果在該方法中呼叫了其他方法,對應的新的棧幀會被創建出來,放在棧的頂端,成為新的當前棧幀。

8.不同執行緒中所包含的棧幀是不允許相互引用的,即不可能在另一個棧幀中引用另外一個執行緒的棧幀

9.如果當前方法呼叫了其他方法,方法返回之際,當前棧幀會傳回此方法的執行結果給前一個棧幀,接著,虛擬機器會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀

10.Java方法有兩種返回函式的方式,一種是正常的函式返回,使用return指令;另外一種是丟擲異常。不管使用哪種方式,都會導致棧幀被彈出。

程式碼示例:

/**
 * 棧幀
 */
public class StackFrameTest {
    public static void main(String[] args) {
        StackFrameTest test = new StackFrameTest();
        test.method1();
        //輸出 method1()和method2()都作為當前棧幀出現了兩次,method3()一次
//        method1()開始執行。。。
//        method2()開始執行。。。
//        method3()開始執行。。。
//        method3()執行結束。。。
//        method2()執行結束。。。
//        method1()執行結束。。。
    }

    public void method1(){
        System.out.println("method1()開始執行。。。");
        method2();
        System.out.println("method1()執行結束。。。");
    }

    public int method2(){
        System.out.println("method2()開始執行。。。");
        int i = 10;
        int m = (int) method3();
        System.out.println("method2()執行結束。。。");
        return i+m;
    }

    public double method3(){
        System.out.println("method3()開始執行。。。");
        double j = 20.0;
        System.out.println("method3()執行結束。。。");
        return j;
    }

}

虛擬機器棧的相關面試題

1.舉例棧溢位的情況?(StackOverflowError)

  • 遞迴呼叫等,通過-Xss設定棧的大小;

2.調整棧的大小,就能保證不出現溢位麼?

  • 不能如遞迴無限次數肯定會溢位,調整棧大小隻能保證溢位的時間晚一些,極限情況會導致OOM記憶體溢位(Out Of MemeryError)注意是Error

3.分配的棧記憶體越大越好麼?

  • 不是會擠佔其他執行緒的空間
4.垃圾回收是否會涉及到虛擬機器棧?
  • 不會

  • 關於Error我們再多說一點,上面的討論不涉及Exception
  • 首先Exception和Error都是繼承於Throwable 類,在 Java 中只有 Throwable 型別的例項才可以被丟擲(throw)或者捕獲(catch),它是異常處理機制的基本組成型別。

  • Exception和Error體現了JAVA這門語言對於異常處理的兩種方式。
  • Exception是java程式執行中可預料的異常情況,咱們可以獲取到這種異常,並且對這種異常進行業務外的處理。

  • Error是java程式執行中不可預料的異常情況,這種異常發生以後,會直接導致JVM不可處理或者不可恢復的情況。所以這種異常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。

  • 其中的Exception又分為檢查性異常和非檢查性異常。兩個根本的區別在於,檢查性異常 必須在編寫程式碼時,使用try catch捕獲(比如:IOException異常)。非檢查性異常 在程式碼編寫使,可以忽略捕獲操作(比如:ArrayIndexOutOfBoundsException),這種異常是在程式碼編寫或者使用過程中通過規範可以避免發生的。

5.方法中定義的區域性變數是否執行緒安全?

  • 要具體情況具體分析
/**
 * 面試題:
 * 方法中定義的區域性變數是否執行緒安全?具體情況具體分析
 *
 * 何為執行緒安全?
 *     如果只有一個執行緒可以操作此資料,則必定是執行緒安全的。
 *     如果有多個執行緒操作此資料,則此資料是共享資料。如果不考慮同步機制的話,會存線上程安全問題
 *
 * 我們知道StringBuffer是執行緒安全的原始碼中實現synchronized,StringBuilder原始碼未實現synchronized,在多執行緒情況下是不安全的
* 二者均繼承自AbstractStringBuilder
*
*/ public class StringBuilderTest { //s1的宣告方式是執行緒安全的,s1在方法method1內部消亡了 public static void method1(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); } //stringBuilder的操作過程:是不安全的,因為method2可以被多個執行緒呼叫 public static void method2(StringBuilder stringBuilder){ stringBuilder.append("a"); stringBuilder.append("b"); } //s1的操作:是執行緒不安全的 有返回值,可能被其他執行緒共享 public static StringBuilder method3(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); return s1; } //s1的操作:是執行緒安全的 ,StringBuilder的toString方法是建立了一個新的String,s1在內部消亡了 public static String method4(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); return s1.toString(); } public static void main(String[] args) { StringBuilder s = new StringBuilder(); new Thread(()->{ s.append("a"); s.append("b"); }).start(); method2(s); } }