Java - 區域性變數和StackOverflowError
本文目的
對Java棧記憶體進行簡單學習總結,並瞭解 -Xss
JVM引數的配置,學會在程式碼中儘量減少不必要的區域性變數宣告,從而提高程式效率和編碼水平。
Java棧記憶體簡介
Java棧記憶體空間中主要存放的是區域性變數,包括基本資料型別(int
、short
、byte
、long
、float
、double
、char
、boolean
)和引用資料型別。例如:int a = 1
或者 double x = 0.01
這類程式碼宣告的變數將會直接存放在棧空間中;而 Date today = new Date()
則會將引用物件 today
放在棧記憶體中,它引用的真正物件 Date()
引用物件型別可能是一個指向物件起始地址的引用指標,也可能是一個指向代表物件的控制代碼或其他與此物件相關的位置和 returnAddress
型別(指向了一條位元組碼指令的地址)。
64位的 long
和 double
會佔用 2
個區域性變數空間,其他型別只佔用 1 個區域性變數空間。
StackOverFlowError 什麼時候會發生
如果我們在一段程式裡面分配大量的區域性變數,就可能造成棧記憶體空間不足,引發 java.lang.StackOverFlowError
錯誤。要模擬一個 StackOverFlowError
那麼,怎麼設定這個棧記憶體空間的大小呢,那就是 -Xss
引數。
-Xss引數的配置
-Xss
引數用來設定棧記憶體空間的大小,例如 -Xss128K
指分配 128K
的棧記憶體大小。為什麼是 128K
而不是 1K
或者 10K
呢?這個數值的大小可以隨便設定嗎?
我們不妨嘗試一下,隨便寫一個含有 main
方法的Java程式,然後在 IDEA
中執行,指定 VM引數
為 -Xss10K
,如下圖所示:
執行程式,會得到如下錯誤(注意如果設定為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
這個例子中,遞迴方法體裡面有 x1
、x2
兩個區域性變數。加入 -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 個區域性變數空間。
因此,我們應當在編寫程式的過程中,合理地使用棧空間,儘量減少不必要的區域性變數分配,特別是在遞迴方法中尤其要謹慎使用區域性變數。