根本原因
总结就是一句话请求的栈深度大于虚拟机所允许的最大深度。(详情请翻到最下面!!!)
具体情况
目前我一共遇到过四种情况下的java.lang.StackOverflowError异常,全部列举如下:
- 不恰当的递归方法;
- 两个方法循环调用;
- 循环调用构造方法;
- 程序自动循环调用toString()方法;
1.不恰当的递归方法
写过递归方法的我们都知道,对于递归方法来说,为防止其无休止地进行,必须在方法内有终止条件。比如就用递归方法实现斐波那契数列为例:
public int Fibonacci(int num){
if(num == 1 || num == 2)
return num;
return Fibonacci(num - 1) + Fibonacci(num - 2);
}
当num等于1或者num等于2的时候Fibonacci()方法的递归调用就会停下来。
如果在方法内没有终止条件,比如说下面这样,就会发生StackOverflowError。
public int Fibonacci(int num) {
return Fibonacci(num - 1) + Fibonacci(num - 2);
}
这个是控制台打印结果:
Exception in thread "main" java.lang.StackOverflowError
at jvm.OOM.math.Fibonacci(StackOverFlow.java:6)
at jvm.OOM.math.Fibonacci(StackOverFlow.java:6)
at jvm.OOM.math.Fibonacci(StackOverFlow.java:6)
···
2.两个方法循环调用
我们写一个最简单的例子:
class Person {
public void a() {
b();
}
public void b() {
a();
}
}
public class StackOverFlow {
public static void main(String[] args) {
Person p = new Person();
p.a();
}
}
控制台打印结果:
Exception in thread "main" java.lang.StackOverflowError
at jvm.OOM.Person.b(StackOverFlow.java:9)
at jvm.OOM.Person.a(StackOverFlow.java:5)
at jvm.OOM.Person.b(StackOverFlow.java:9)
at jvm.OOM.Person.a(StackOverFlow.java:5)
···
3.循环调用构造方法
class Person{
Person p = new Person();
}
public class StackOverFlow {
public static void main(String[] args) {
Person p = new Person();
}
}
控制台打印:
Exception in thread "main" java.lang.StackOverflowError
at oom.Person.<init>(StackOverFlow.java:4)
at oom.Person.<init>(StackOverFlow.java:4)
at oom.Person.<init>(StackOverFlow.java:4)
at oom.Person.<init>(StackOverFlow.java:4)
at oom.Person.<init>(StackOverFlow.java:4)
···
之所以会犯这个错误,是我刚开始学习synchronize关键字的时候,其中当synchronize关键字作用于代码块时,要给定一个对象,我当时就new了自身,所以导致了StackOverflowError异常。详见:Java多线程入门:线程安全与synchronized关键字
当时的错误代码如下所示:
class Shopping implements Runnable {
private int num = 10;
//会导致StackOverflowError异常
Shopping shop = new Shopping();
public void run() {
while (true) {
synchronized (shop) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "抢血拼到了第" + num-- + "双鞋");
} else
break;
}
}
}
}
4.程序自动循环调用toString()方法
因为打印对象的时候会自动调用对象的toString()方法。当两个对象相互引用的时候,就会无限循环调用toString()方法,从而造成StackOverflowError异常。
class Reader {
String name;
String id;
Book books;
public Reader(String name, String id) {
this.name = name;
this.id = id;
}
public String toString() {
return "[name=" + name + ", id=" + id + ", books=" + books + "]";
}
}
class Book {
String name;
Reader readers;
public Book(String name, Reader reader) {
this.name = name;
this.readers = reader;
}
public String toString() {
return "[name=" + name + ", readers=" + readers + "]";
}
}
public class StackOverFlow {
public static void main(String[] args) {
Reader reader = new Reader("张三", "33");
Book book = new Book("三国志", reader);
reader.books = book;
System.out.println(reader);
}
}
控制台打印结果:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.AbstractStringBuilder.append(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at java.lang.StringBuilder.<init>(Unknown Source)
at jvm.OOM.Reader.toString(StackOverFlow.java:15)
at java.lang.String.valueOf(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at jvm.OOM.Book.toString(StackOverFlow.java:30)
at java.lang.String.valueOf(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at jvm.OOM.Reader.toString(StackOverFlow.java:15)
at java.lang.String.valueOf(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at jvm.OOM.Book.toString(StackOverFlow.java:30)
···
如果看到这里的您还有什么高见,欢迎补充哦!!!!!!!!!!今天也是奋斗的一天!!!!!!!!
详情
能翻到这里的朋友,肯定是想详细了解发生StackOverflowError的底层原因。那我们就多说一点。
首先,一般情况下我们对Java虚拟机的区域划分就是栈、堆、方法区。实际上,详细的划分如下图所示有:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器。
第二,发生StackOverflowError的数据区域就是Java虚拟机栈。在《Java虚拟机规范》中是这么描述的,
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
第三,为什么栈深度会大于虚拟机所允许的最大深度?
那我们就要了解Java虚拟机栈的作用。首先每个方法在执行的同时都会创建一个栈帧;第二每个方法从调用到执行完成的过程,就对应这个栈帧在Java虚拟机栈中入栈到出栈的过程;第三既然Java虚拟机栈是一个数据区域那么肯定是有大小限制的,不可能无限大。
所以如果同时要执行的方法非常多的话(上面四种情况),每个方法对应虚拟机栈中的一个栈帧,肯定会挤爆这个区域。