Java之深入JVM(3) - 由一个栈溢出的问题看Java类和对象的初始化 (转)


今天,在一个群里面有网友问到这样一个问题,以下代码被调用运行时为何会造成栈溢出(StackOverflowError)的错误:

public class Constructor {
    Constructor c = new Constructor();

    public static void main(String[] args) {
        Constructor test = new Constructor();

    }
}
一般人,初看感觉没啥问题,但是自己在机器上跑了一下,就会爆出这样的错误,如图
stackoverflowerror 
 
从这些错误中我们可以得到这样一个信息:程序运行时候,Constructor实例初始化方法
(在这里就是<init>,这个后面还会细讲),被疯狂的调用。
 
群里面的人,对这个问题的回答,其中有个网友通过现象推结果,说是:“在这个Constuctor类中,由于类的成员c本身就是
Constructor类型的,所以当类的成员初始化时,类的构造函数就被递归调用了”。 
 
这个回答,说实话挺没逻辑的,看的我比较云里雾里。其实这个问题如果我们从反汇编后的该类的字节码入手,
就能很清楚的得到问题的答案了.
我们用java –p Constructor 得到反汇编后的字节码,如下:
复制代码
public   class  Constructor  extends  java.lang.Object{
Constructor c;

public  Constructor();
  Code:
   
0 :   aload_0
   
1 :   invokespecial   # 10 // Method java/lang/Object."<init>":()V
    4 :   aload_0
   
5 :    new      # 1 // class Constructor
    8 :   dup
   
9 :   invokespecial   # 12 // Method "<init>":()V
    12 :  putfield        # 13 // Field c:LConstructor;
    15 :   return

public   static   void  main(java.lang.String[]);
  Code:
   
0 :    new      # 1 // class Constructor
    3 :   dup
   
4 :   invokespecial   # 12 // Method "<init>":()V
    7 :   astore_1
   
8 :    return

}
复制代码
 
 
   我们只要关注此类的构造方法Constructor中的代码就行了,我们可以发现在这构造方法里面,
出现了new #1;//class Constructor 这样的语句,他表示创建一个Constructor类型的对象。
 
从这里面我们便可以明白:
即便你在构造函数外面,显式的初始化了一个成员如c,但是类编译后运行时,
这种显式初始化成员的真正初始化还是放在构造函数中,统一进行的。
 
所以像刚才的那种代码,相当于就是在Constructor构造函数里面调用了自身.就像下面代码一样:
复制代码
public   class  Constructor {

    Constructor c;

    
public  Constructor() {
        c 
=   new  Constructor();
    }

    
public   static   void  main(String[] args) {
        Constructor test 
=   new  Constructor();

    }
}
复制代码
 
你说怎么可能不栈溢出呢?
PS:顺便补充一下,几条Bytecode指令的意思: new 创建一个新对象.
       invokespecial  根据编译时类型来调用实例方法.
       invokevirtual   根据运行时对象实际类型,来调用实例方法.
       putfield          设置对象中字段的值.  
 
 
智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告