Java - 局部变量和StackOverflowError

Java - 局部变量和StackOverflowError

2018-10-15 17:17:54 Java 5 0

本文目的

对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)

对比及思考

栈空间大小局部变量类型局部变量个数调用深度
104Klong2785
104Klong10363
104Kint10551

通过以上例子可以看出: - 在相同的栈内存空间下,局部变量越少,可以递归调用的次数越多 - 相反,如果有过多的局部变量,则会增加栈内存的开销

同时,long 类型是64位,它会占用 2 个局部变量空间,而 int 占用的是 1 个局部变量空间。

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

赞赏支持!
jvmxssStackOverflowError
上一篇:详解设计模式 - 工厂模式(3种) 下一篇:Java8 - 更优雅的字符串连接(join)收集器 Collectors.joining

文章评论

欢迎一起交流

评论框加载中......