Java虚拟机栈溢出是指JVM无法分配足够的栈内存空间而产生的堆栈上溢错误。它会在线程请求的栈深度超过虚拟机允许的最大深度时发生。
导致栈溢出的常见原因有:
- 过深的递归调用:每进行一次递归调用,栈帧就会入栈一次。如果递归调用层数过深,最终会导致栈溢出。
- 线程数量过多:每个线程都有自己的栈内存,如果创建过多线程,最终内存占用超过JVM允许的最大值,从而产生溢出。
- 本地方法调用:JVM中对本地方法调用的栈帧数量没有限制,如果本地方法递归调用过深,也会导致栈溢出。
要避免栈溢出,我们可以:
- 避免过深递归调用,保持递归层数在合理范围内。可以改用循环替代过深递归。
- 避免创建过多线程,特别在递归调用的场景下。可以通过线程池来限制线程数量。
- 监控本地方法调用的栈帧深度,避免递归调用过深。
- 增大线程栈内存大小,-Xss参数可以用于此目的。但是不建议超过4MB,否则会减少可创建线程数量。
- 使用标记-清除或标记-整理算法的垃圾收集器可以合理利用内存,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();
}
}
}
运行这两个程序,都会产生栈溢出异常。