Java之深入JVM(3) - 由一個棧溢出的問題看Java類和對象的初始化

今天,在一個羣裏面有網友問到這樣一個問題,以下代碼被調用運行時爲何會造成棧溢出(StackOverflowError)的錯誤:

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

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

    }
}
一般人,初看感覺沒啥問題,但是自己在機器上跑了一下,就會爆出這樣的錯誤,如圖
 
 
從這些錯誤中我們可以得到這樣一個信息:程序運行時候,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          設置對象中字段的值.  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章