Java代码触发StackOverflowError的原因困惑及原理求证
代码触发StackOverflowError的原因解析
Java对象初始化的正确顺序
执行new 类名()时,JVM的完整初始化流程是:
- 为新对象分配堆内存空间
- 给所有实例变量赋予默认初始值(引用类型为
null,基本类型对应默认值,比如int为0) - 执行实例变量的显式初始化代码(如
private int age = 30;)和实例初始化块 - 调用类的构造函数执行剩余初始化逻辑
注意:编译器会把步骤3的代码合并到构造函数的最开头,也就是说,实例变量初始化属于构造函数执行流程的一部分,对应字节码中的<init>方法。
你的代码执行流程拆解
当main方法执行Human h = new Human();时:
- JVM分配堆内存,给当前
Human实例的变量赋默认值:name=null,age=0,human=null - 执行实例变量显式初始化:
- 先执行
age = 30;,把age的值设为30 - 接着执行
human = new Human();,这会再次触发new Human()的完整初始化流程
- 先执行
- 上述步骤会无限递归:每次
new Human()都会走到实例变量初始化阶段,再次调用new Human(),永远无法执行到构造函数中的System.out.println("hello world");代码
核心疑问解答
为什么是StackOverflowError:
每次执行new Human()都会触发构造函数(字节码<init>方法)的调用,实例变量初始化是构造函数流程的一部分。递归调用<init>方法会不断在调用栈中生成新的方法帧,栈的内存空间固定且有限,很快就会被填满,触发StackOverflowError。你没看到构造函数的输出,是因为递归发生在构造函数核心代码执行之前,但调用栈已经被递归的方法帧占满了。为什么资料说构造函数调用导致溢出:
你的误解在于认为实例变量初始化和构造函数完全分开,但实际上实例变量显式初始化被编译器合并到了构造方法中,递归的根源是每次构造函数执行时都会触发新的构造函数调用,所以这个结论是准确的。为什么不是OutOfMemoryError:
堆内存默认空间远大于栈空间,而且每次递归中创建的对象还没完成初始化(卡在了实例变量初始化的递归步骤),栈会先被耗尽,因此堆溢出不会先发生。
内容的提问来源于stack exchange,提问作者NinaJava
相关产品推荐
相关产品推荐

