• 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          设置对象中字段的值.  
     
     
  • 相关阅读:
    UltraSoft
    UltraSoft
    UltraSoft
    UltraSoft
    UltraSoft
    2020软工提问回顾与个人总结作业
    2020软工结对项目作业-简单几何形状间交点统计
    2020软工个人博客作业-博客园班级博客分析
    2020软工个人阅读博客作业
    2020软工第一次作业-热身
  • 原文地址:https://www.cnblogs.com/royi123/p/3132086.html
Copyright © 2020-2023  润新知