簡潔回答:
- 第一步,判斷常量池是否能定位類的符號引用,并且檢查這個符號引用代表的類是否被加載、解析、初始化過。如果沒有則執(zhí)行第一步,如果有則執(zhí)行第二步
- 第二步類加載和初始化(初次使用),具體可分為加載、驗證、準備、解析、初始化
- 第三步創(chuàng)建對象,具體可分為實例對象分配內(nèi)存、賦值、執(zhí)行實例初始化代碼、返回引用
詳細回答
第一步
判斷常量池是否能定位類的符號引用,并且檢查這個符號引用代表的類是否被加載、解析、初始化過。如果沒有則執(zhí)行第一步,如果有則執(zhí)行第二步
第二步,類加載和初始化(初次使用)
如果類已經(jīng)被加載過,則不會執(zhí)行第一步整個步驟
1. 加載
- 類加載器根據(jù)類的全限定名來讀取此類的二進制字節(jié)流到JVM內(nèi)部,并存儲在運行時內(nèi)存區(qū)的方法區(qū),然后將其轉(zhuǎn)換為一個與目標類型對應(yīng)的java.lang.Class對象實例
2. 驗證
- 格式驗證:驗證是否符合class文件規(guī)范,比如必須以魔數(shù)0xCAFEBABE開頭
- 語義驗證:檢查一個被標記為final的類型是否包含子類;檢查一個類中的final方法是否被子類進行重寫;確保父類和子類之間沒有不兼容的一些方法聲明(比如方法簽名相同,但方法的返回值不同)
- 操作驗證:在操作數(shù)棧中的數(shù)據(jù)必須進行正確的操作,對常量池中的各種符號引用執(zhí)行驗證(通常在解析階段執(zhí)行,檢查是否可以通過符號引用中描述的全限定名定位到指定類型上,以及類成員信息的訪問修飾符是否允許訪問等)
3. 準備
- 為類中的所有靜態(tài)變量/常量分配內(nèi)存空間,并為其設(shè)置一個初始值(由于還沒有產(chǎn)生對象,實例變量不在此操作范圍內(nèi))
public static int value= 1,初始化后的值為 0
public static final int value= 1,初始化后的值為 1
4. 解析
- 將常量池中的符號引用轉(zhuǎn)為直接引用(得到類或者字段、方法在內(nèi)存中的指針或者偏移量,以便直接調(diào)用該方法)
符號引用:比如我們規(guī)定了json字符串,"{}"表示對象,"[]"表示數(shù)組,"{}"和"[]"就相當(dāng)于符號引用。在JVM中符號引用是用一組符號描述所引用的目標,比如用0x4000聲明這是一個枚舉類型
直接引用:內(nèi)存地址(指針/偏移量/句柄)
5. 初始化(先父類,再子類)
- 執(zhí)行類構(gòu)造器 init 方法,init方法包含 為靜態(tài)變量賦值、執(zhí)行static代碼塊
第三步,創(chuàng)建對象
1. 在堆區(qū)為實例對象分配內(nèi)存
- 為實例變量分配內(nèi)存(包括本類和父類),但不包括任何靜態(tài)變量
2. 對實例變量賦默認值
- 將方法區(qū)內(nèi)對實例變量的定義拷貝一份到堆區(qū),然后賦默認值
3. 執(zhí)行實例初始化代碼
- 先初始化父類再初始化子類,初始化時先執(zhí)行非靜態(tài)代碼塊(包括非靜態(tài)初始化塊,非靜態(tài)屬性)再執(zhí)行構(gòu)造方法
非靜態(tài)代碼塊也叫做實例代碼塊
4. 將堆區(qū)對象的地址賦值給棧區(qū)的引用變量
- 有類似于Child c = new Child()形式的引用,將堆區(qū)對象的地址賦值給棧區(qū)的引用變量c
流程圖
