Java之深入JVM(3) - 由一个栈溢出的问题看Java类和对象的初始化 (转)
2013-06-11 21:29
381 查看
今天,在一个群里面有网友问到这样一个问题,以下代码被调用运行时为何会造成栈溢出(StackOverflowError)的错误:
publicclassConstructorextendsjava.lang.Object{
Constructorc;
publicConstructor();
Code:
0:aload_0
1:invokespecial#10;//Methodjava/lang/Object."<init>":()V
4:aload_0
5:new#1;//classConstructor
8:dup
9:invokespecial#12;//Method"<init>":()V
12:putfield#13;//Fieldc:LConstructor;
15:return
publicstaticvoidmain(java.lang.String[]);
Code:
0:new#1;//classConstructor
3:dup
4:invokespecial#12;//Method"<init>":()V
7:astore_1
8:return
}
publicclassConstructor{
Constructorc;
publicConstructor(){
c=newConstructor();
}
publicstaticvoidmain(String[]args){
Constructortest=newConstructor();
}
}
你说怎么可能不栈溢出呢?
publicclassConstructor{ Constructorc=newConstructor(); publicstaticvoidmain(String[]args){ Constructortest=newConstructor(); } }
一般人,初看感觉没啥问题,但是自己在机器上跑了一下,就会爆出这样的错误,如图
从这些错误中我们可以得到这样一个信息:程序运行时候,Constructor实例初始化方法
(在这里就是<init>,这个后面还会细讲),被疯狂的调用。
群里面的人,对这个问题的回答,其中有个网友通过现象推结果,说是:“在这个Constuctor类中,由于类的成员c本身就是
Constructor类型的,所以当类的成员初始化时,类的构造函数就被递归调用了”。
这个回答,说实话挺没逻辑的,看的我比较云里雾里。其实这个问题如果我们从反汇编后的该类的字节码入手,
就能很清楚的得到问题的答案了.
我们用java–pConstructor得到反汇编后的字节码,如下:
publicclassConstructorextendsjava.lang.Object{
Constructorc;
publicConstructor();
Code:
0:aload_0
1:invokespecial#10;//Methodjava/lang/Object."<init>":()V
4:aload_0
5:new#1;//classConstructor
8:dup
9:invokespecial#12;//Method"<init>":()V
12:putfield#13;//Fieldc:LConstructor;
15:return
publicstaticvoidmain(java.lang.String[]);
Code:
0:new#1;//classConstructor
3:dup
4:invokespecial#12;//Method"<init>":()V
7:astore_1
8:return
}
我们只要关注此类的构造方法Constructor中的代码就行了,我们可以发现在这构造方法里面,
出现了new#1;//classConstructor这样的语句,他表示创建一个Constructor类型的对象。
从这里面我们便可以明白:
即便你在构造函数外面,显式的初始化了一个成员如c,但是类编译后运行时,
这种显式初始化成员的真正初始化还是放在构造函数中,统一进行的。
所以像刚才的那种代码,相当于就是在Constructor构造函数里面调用了自身.就像下面代码一样:
publicclassConstructor{
Constructorc;
publicConstructor(){
c=newConstructor();
}
publicstaticvoidmain(String[]args){
Constructortest=newConstructor();
}
}
你说怎么可能不栈溢出呢?
PS:顺便补充一下,几条Bytecode指令的意思:new创建一个新对象.
invokespecial根据编译时类型来调用实例方法.
invokevirtual根据运行时对象实际类型,来调用实例方法.
putfield设置对象中字段的值.
相关文章推荐
- Java之深入JVM(3) - 由一个栈溢出的问题看Java类和对象的初始化
- 深入JVM(2): 由一个栈溢出的问题看Java类和对象的初始化
- Java之深入JVM(4) - 由一个栈溢出的问题看Java类和对象的初始化续
- 深入JVM(3):由一个栈溢出的问题看Java类和对象的初始化续
- 浅析一个java对象初始化顺序问题
- 剖析一个java对象初始化顺序问题
- 深入理解Java虚拟机笔记--JVM内存模型及溢出问题总结
- 剖析一个java对象初始化顺序问题
- 解析 Java 类和对象的初始化过程 由一个单态模式引出的问题谈起
- 解析 Java 类和对象的初始化过程(由一个单态模式引出的问题谈起)
- 深入研究Java中一个对象的初始化过程
- 剖析一个java对象初始化顺序问题
- java认证:剖析一个java对象初始化顺序问题
- 深入理解JVM(1)——Java内存区域与Java对象
- 一个java对象的初始化顺序
- JVM 如何装载和初始化一个Java class(类)
- 越深入java 就觉得java做得越烂,java设计问题 之 :关于1.5,1.6 java处理基本对象类型cache的问题
- java继承,多态方面的一个问题,定义一个父类对象,用子类new
- 一个类中静态容器对象的初始化问题
- java通过xstream将xml转换为java对象遇到的问题(一个对像中两个子类的类名相同)