1. 程式人生 > >Java - 區域性變數和StackOverflowError

Java - 區域性變數和StackOverflowError

本文目的

對Java棧記憶體進行簡單學習總結,並瞭解 -Xss JVM引數的配置,學會在程式碼中儘量減少不必要的區域性變數宣告,從而提高程式效率和編碼水平。

Java棧記憶體簡介

Java棧記憶體空間中主要存放的是區域性變數,包括基本資料型別(intshortbytelongfloatdoublecharboolean)和引用資料型別。例如:int a = 1 或者 double x = 0.01 這類程式碼宣告的變數將會直接存放在棧空間中;而 Date today = new Date() 則會將引用物件 today 放在記憶體中,它引用的真正物件 Date()

則會存放在空間中,本文只討論棧記憶體,不討論堆記憶體。

引用物件型別可能是一個指向物件起始地址的引用指標,也可能是一個指向代表物件的控制代碼或其他與此物件相關的位置和 returnAddress 型別(指向了一條位元組碼指令的地址)。

64位的 longdouble 會佔用 2 個區域性變數空間,其他型別只佔用 1 個區域性變數空間。

StackOverFlowError 什麼時候會發生

如果我們在一段程式裡面分配大量的區域性變數,就可能造成棧記憶體空間不足,引發 java.lang.StackOverFlowError 錯誤。要模擬一個 StackOverFlowError

錯誤,最簡單的方法就是使用遞迴。

那麼,怎麼設定這個棧記憶體空間的大小呢,那就是 -Xss 引數。

-Xss引數的配置

-Xss 引數用來設定棧記憶體空間的大小,例如 -Xss128K 指分配 128K 的棧記憶體大小。為什麼是 128K 而不是 1K 或者 10K 呢?這個數值的大小可以隨便設定嗎?

我們不妨嘗試一下,隨便寫一個含有 main 方法的Java程式,然後在 IDEA 中執行,指定 VM引數-Xss10K,如下圖所示:

IDEA設定-Xss引數
IDEA設定-Xss引數

執行程式,會得到如下錯誤(注意如果設定為1K,該引數可能不會生效而是採用初始的棧記憶體大小):

Error: Could not create the Java Virtual Machine.
The stack size specified is too small, Specify at least 104k
Error: A fatal exception has occurred. Program will exit.

可以看出,JVM對於棧記憶體的大小是有最低要求的,不能低於 104K。經測試,當設定稍微低於 104K 的時候,程式有時候也是可以執行的,但儘量不要這樣做。

下面,就通過程式程式碼來實戰 StackOverFlowError 錯誤。

JavaXssDemo1

這個例子中,遞迴方法體裡面有 x1x2 兩個區域性變數。加入 -Xss104k JVM引數,執行以下程式:

/**
 * Java - 棧記憶體大小設定Demo1
 *
 * @author Zebe
 */
public class JavaXssDemo1 {

    /**
     * 遞迴深度
     */
    private static int count = 0;

    /**
     * 遞迴測試(包含少量區域性變數)
     */
    private static void recursionWithFewVariables() {
        long x1 = 1, x2 = 2;
        count++;
        recursionWithFewVariables();
    }

    /**
     * 程式入口
     * -Xss104k
     *
     * @param args 執行引數
     */
    public static void main(String[] args) {
        try {
            recursionWithFewVariables();
        } catch (Throwable e) {
            System.out.println("遞迴測試(包含少量區域性變數long),呼叫深度 = " + count);
            e.printStackTrace();
        }
    }

}

程式輸出結果如下:

遞迴測試(包含少量區域性變數),呼叫深度 = 785
java.lang.StackOverflowError
	at me.zebe.cat.java.jvm.JavaXssDemo1.recursionWithFewVariables(JavaXssDemo1.java:19)

JavaXssDemo2

這個例子中,,將 JavaXssDemo1 中的區域性變數從 2 個增加到 10 個。加入 -Xss104k JVM引數,執行以下程式:

/**
 * Java - 棧記憶體大小設定Demo2
 *
 * @author Zebe
 */
public class JavaXssDemo2 {

    /**
     * 遞迴深度
     */
    private static int count = 0;

    /**
     * 遞迴測試(包含多個區域性變數)
     */
    private static void recursionWithMoreVariables() {
        long x1 = 1, x2 = 2, x3 = 3, x4 = 4, x5 = 5, x6 = 6, x7 = 7, x8 = 8, x9 = 9, x10 = 10;
        count++;
        recursionWithMoreVariables();
    }

    /**
     * 程式入口
     * -Xss104k
     *
     * @param args 執行引數
     */
    public static void main(String[] args) {
        try {
            recursionWithMoreVariables();
        } catch (Throwable e) {
            System.out.println("遞迴測試(包含多個區域性變數long),呼叫深度 = " + count);
            e.printStackTrace();
        }
    }

}

程式輸出結果如下:

遞迴測試(包含多個區域性變數),呼叫深度 = 363
java.lang.StackOverflowError
	at me.zebe.cat.java.jvm.JavaXssDemo2.recursionWithMoreVariables(JavaXssDemo2.java:19)

JavaXssDemo3

這個例子中,將 JavaXssDemo2 中的區域性變數型別由 long 改為 int,數量不變 (還是10個)。加入 -Xss104k JVM引數,執行以下程式:

/**
 * Java - 棧記憶體大小設定Demo3
 *
 * @author Zebe
 */
public class JavaXssDemo3 {

    /**
     * 遞迴深度
     */
    private static int count = 0;

    /**
     * 遞迴測試(包含多個區域性變數)
     */
    private static void recursionWithMoreVariables() {
        int x1 = 1, x2 = 2, x3 = 3, x4 = 4, x5 = 5, x6 = 6, x7 = 7, x8 = 8, x9 = 9, x10 = 10;
        count++;
        recursionWithMoreVariables();
    }

    /**
     * 程式入口
     * -Xss104k
     *
     * @param args 執行引數
     */
    public static void main(String[] args) {
        try {
            recursionWithMoreVariables();
        } catch (Throwable e) {
            System.out.println("遞迴測試(包含多個區域性變數int),呼叫深度 = " + count);
            e.printStackTrace();
        }
    }

}

程式輸出結果如下:

遞迴測試(包含多個區域性變數),呼叫深度 = 551
java.lang.StackOverflowError
	at me.zebe.cat.java.jvm.JavaXssDemo3.recursionWithMoreVariables(JavaXssDemo3.java:19)

對比及思考

棧空間大小 區域性變數型別 區域性變數個數 呼叫深度
104K long 2 785
104K long 10 363
104K int 10 551

通過以上例子可以看出:

  • 在相同的棧記憶體空間下,區域性變數越少,可以遞迴呼叫的次數越多
  • 相反,如果有過多的區域性變數,則會增加棧記憶體的開銷

同時,long 型別是64位,它會佔用 2 個區域性變數空間,而 int 佔用的是 1 個區域性變數空間。

因此,我們應當在編寫程式的過程中,合理地使用棧空間,儘量減少不必要的區域性變數分配,特別是在遞迴方法中尤其要謹慎使用區域性變數