本文目的
对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 个局部变量空间。
因此,我们应当在编写程序的过程中,合理地使用栈空间,尽量减少不必要的局部变量分配,特别是在递归方法中尤其要谨慎使用局部变量。
本文原文地址:https://blog.youkuaiyun.com/zebe1989/article/details/83340694