??Java 是面向對象的靜態強類型語言,聲明并創建對象的代碼很常見,根據某個類聲明一個引用變量指向被創建的對象,并使用此引用變量操作該對象。在實例化對象的過程中,JVM 中發生了什么化學反應呢?
1.下面從最簡單的 Object ref= new Object();代碼進行分析,利用javap -verbose -p 命令查看對象創建的字節碼如下:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 ref Ljava/lang/Object;
- NEW: 如果找不到Class對象,則進行類加載。加載成功后,則在堆中分配內存,從 Object 開始到本類路徑上的所有屬性值都要分配內存。分配完畢之后,進行零值初始化。在分配過程中,注意引用是占據存儲空間的,它是一個變量,占用4個字節。這個指令完畢后,將指向實例對象的引用變量壓入虛擬機棧頂。
- DUP:在棧頂復制該引用變量,這時的棧頂有兩個指向堆內實例對象的引用變量。如果 <init> 方法有參數,還需要把參數壓入操作棧中。兩個引用變量的目的不同,其中壓至底下的引用用于賦值,或者保存到局部變量表,另個棧頂的引用變量作為句柄調用相關方法。
- INVOKESPECIAL: 調用對象實例方法,通過棧頂的引用變量調用<init>方法。<clinit> 是類初始化時執行的方法,而 <init> 是對象初始化時執行的方法。
2.前面所述是從字節碼的角度看待對象的創建過程,現在從執行步驟的角來分析。
- 確認類元信息是否存在。當JVM 接收到 new 指令時,首先在 metaspace內查需要創建的類元信息是否存在。若不存在,那么在雙親委派模式下,使用當前類加載器以ClassLoader+ 包名+類名為 Key進行查找對應的class文件如果沒有找到文件,則拋出 ClassNotFoundException 異常;如果找到,則講行類加載,并生成對應的Class 類對象。
- 分配對象內存。首先計算對象占用空間大小,如果實例成員變量是引用變量,僅分配引用變量空間即可,即 4 個字節大小,接著在堆中劃分一塊內有給新對象。在分配內存空間時,需要進行同步操作,比如采用CAS( Compar And Swap)失敗重試、區域加鎖等方式保證分配操作的原子性。
- 設定默認值。成員變量值都需要設定為默認值,即各種不同形式的零值。
- 設置對象頭。設置新對象的哈希碼、GC信息、鎖信息、對象所屬的類元信息等這個過程的具體設置方式取決于JVM實現。
- 執行 init 方法。初始化成員變量,執行實例化代碼塊,調用類的構造方法并把堆內對象的首地址賦值給引用變量。