什么是Java虚拟机栈溢出?如何避免栈溢出?代码举例讲解

Java虚拟机栈溢出是指JVM无法分配足够的栈内存空间而产生的堆栈上溢错误。它会在线程请求的栈深度超过虚拟机允许的最大深度时发生。

导致栈溢出的常见原因有:

  1. 过深的递归调用:每进行一次递归调用,栈帧就会入栈一次。如果递归调用层数过深,最终会导致栈溢出。
  2. 线程数量过多:每个线程都有自己的栈内存,如果创建过多线程,最终内存占用超过JVM允许的最大值,从而产生溢出。
  3. 本地方法调用:JVM中对本地方法调用的栈帧数量没有限制,如果本地方法递归调用过深,也会导致栈溢出。

要避免栈溢出,我们可以:

  1. 避免过深递归调用,保持递归层数在合理范围内。可以改用循环替代过深递归。
  2. 避免创建过多线程,特别在递归调用的场景下。可以通过线程池来限制线程数量。
  3. 监控本地方法调用的栈帧深度,避免递归调用过深。
  4. 增大线程栈内存大小,-Xss参数可以用于此目的。但是不建议超过4MB,否则会减少可创建线程数量。
  5. 使用标记-清除或标记-整理算法的垃圾收集器可以合理利用内存,ometerslen减少溢出风险。

来看一个简单例子:我们可以编写两个Java程序来模拟栈溢出的场景,一个是递归调用导致,一个是过多线程导致。

递归调用栈溢出:

public class StackOverflowByRecursion {
    public void recur() {
        recur(); 
    }

    public static void main(String[] args) {
        StackOverflowByRecursion so = new StackOverflowByRecursion();
        so.recur();
    } 
}

线程数量过多栈溢出:

public class StackOverflowByThreads {
    public void call() {
        // do nothing
    }

    public static void main(String[] args) {
        StackOverflowByThreads so = new StackOverflowByThreads();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {
            threads.add(new Thread(so::call));
        }

        for (Thread t : threads) {
            t.start();
        }
    }
}

运行这两个程序,都会产生栈溢出异常。