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 个局部变量空间。
因此,我们应当在编写程序的过程中,合理地使用栈空间,尽量减少不必要的局部变量分配,特别是在递归方法中尤其要谨慎使用局部变量。