類的生命周期
類的生命周期分為以下7個階段:加載 -> 驗證 -> 準(zhǔn)備 -> 解析 -> 初始化 -> 使用 -> 卸載,其中驗證、準(zhǔn)備、解析階段又統(tǒng)稱為連接。

一、加載
加載階段有以下三個步驟:
- 通過類全限定名獲取這個類的二進制字節(jié)流
- 將字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
PS:在HotSpot中,第三階段生成的Class對象是保存在方法區(qū)的。
二、驗證
驗證是連接階段的第一步,這個階段的目的是檢查字節(jié)流中包含的信息是否符合虛擬機的要求,確保不會危害虛擬機自身的安全。它包括如下四個方面的驗證:文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證。
1.文件格式驗證
第一階段要驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能夠被虛擬機處理,要驗證的方面有:
是否以魔數(shù)0xCAFEBABE開頭
主、次版本號是否在當(dāng)前虛擬機處理范圍內(nèi)
常量池中的常量是否有不被支持的常量類型
指向常量的索引值是否有不存在的常量或者不符合類型的常量
CONSTANT_Utf8_info型的常量是否符合UTF-8編碼格式
......
除了以上這些,還有很多需要驗證的地方,上面只是一小部分。通過了這個階段的驗證,字節(jié)流中的靜態(tài)數(shù)據(jù)結(jié)構(gòu)會保存在方法區(qū)中,后面三個階段的驗證都是基于方法區(qū)的存儲結(jié)構(gòu)進行的。
2.元數(shù)據(jù)驗證
第二階段對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求,要驗證的方面有:
這個類是否具有父類
這個類的父類是否繼承了不被繼承的類
如果這個類不是抽象類,是否實現(xiàn)了父類或接口中要求實現(xiàn)的所有方法
類中的字段、方法是否矛盾(例如覆蓋了父類的final字段,或者方法重載不符合規(guī)則)
......
3.字節(jié)碼驗證
第三階段將對類的方法體進行校驗分析,保證方法是合法的、符合邏輯的,不會做出危害虛擬機的行為,要驗證的方面有:
保證任意時刻操作數(shù)棧的數(shù)據(jù)類型和指令序列都能配合工作,例如不會出現(xiàn)這樣的情況:對int型數(shù)據(jù)操作卻用了對float型數(shù)據(jù)進行操作的指令
保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼上
保證類型轉(zhuǎn)換是正確的,例如可以把一個子類對象賦值給父類變量,但反過來不可以
......
4.符號引用驗證
第四階段可以看作是對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗,要驗證的方面有:
根據(jù)類的全限定名字符串能否找到相應(yīng)的類
在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
符號引用中的類、字段、方法能否被當(dāng)前類訪問
......
三、準(zhǔn)備
準(zhǔn)備階段是為類變量(靜態(tài)變量)分配內(nèi)存空間并設(shè)置變量初始值,這里的初始值是虛擬機內(nèi)部設(shè)置的,并不是用戶給變量賦予的值,這里也可以說是設(shè)置零值,例如int型變量的零值是0,float、double型變量的零值是0.0,更多類型的零值如下表:

假如變量是final的,并且是基本數(shù)據(jù)類型或者String類型,那在準(zhǔn)備階段就會設(shè)置為被顯式賦予的值。
四、解析
解析是把符號引用替換為直接引用的過程。
符號引用:通俗的講,符號引用可以說是一些字符串,比如com.example.ClassA中使用了com.example.ClassB,那么ClassA的字節(jié)碼會出現(xiàn)com/example/ClassB這樣的字符串(符號)來說明引用了com.example.ClassB。
直接引用:直接引用可以是
- 指向目標(biāo)的指針(比如:指向類型[Class對象]、指向類變量、指向類方法的直接引用可能是指向方法區(qū)的指針)
- 相對偏移量(比如指向?qū)嵗兞?、實例方法的直接引用都是偏移量?/li>
- 一個能間接定位到目標(biāo)的句柄
1.類或接口的解析
-
解析的類不是數(shù)組
假如當(dāng)前類是A,要解析類B,虛擬機會把類B的全限定名傳遞給加載類A的類加載器去加載它。 -
解析的類是數(shù)組
假如當(dāng)前類是A,要解析類B的數(shù)組,會像上面那樣加載類B,最后生成一個代表此數(shù)組維度和元素的對象。 -
確保有訪問權(quán)限
最后進行符號引用驗證,確認(rèn)有解析的類的訪問權(quán)限。
2.字段解析
對字段的解析,要先解析該字段所在的類或接口(假設(shè)該類為C),再按下面步驟搜索字段:
① 如果C包含了簡單名稱和描述符都符合的字段,則返回這個字段的直接引用。
② 否則,查找C實現(xiàn)的接口(遞歸搜索該接口的父接口),如果有簡單名稱和描述符都符合的字段,則返回這個字段的直接引用。
③ 否則,如果C不是java.lang.Object,則查找C的父類,如果有簡單名稱和描述符都符合的字段,則返回這個字段的直接引用。
④ 否則,查找失敗,拋出java.lang.NoSuchFieldException異常。
查找到匹配的字段時,要檢查是否有該字段的訪問權(quán)限,如果沒有,則拋出java.lang.IllegalAccessError異常。
3.類方法解析
對類方法的解析,也是要先解析該方法所在的類(假設(shè)該類為C),再按下面步驟搜索方法:
① 首先檢查C是不是一個類,如果發(fā)現(xiàn)類C是個接口,會拋出java.lang.IncompatibleClassChangeError異常。
② 如果C中有簡單名稱和描述符都符合的方法,則返回此方法的直接引用。
③ 否則,如果C不是java.lang.Object,則遞歸查找C的父類,如果有簡單名稱和描述符都符合的方法,則返回這個方法的直接引用。
④否則,查找C實現(xiàn)的接口(遞歸搜索該接口的父接口),如果有簡單名稱和描述符都符合的方法,說明該方法是抽象方法,C是抽象類,拋出java.lang.AbstractMethodError異常。
⑤ 否則,查找失敗,拋出java.lang.NoSuchMethodError異常。
查找到匹配的方法時,要檢查是否有該方法的訪問權(quán)限,如果沒有,則拋出java.lang.IllegalAccessError異常。
4.接口方法解析
對接口方法的解析,也是要先解析該方法所在的接口(假設(shè)接口為I),再按下面步驟搜索方法:
① 先檢查I是不是一個接口,如果I是一個類,會拋出java.lang.IncompatibleClassChangeError異常。
② 否則,如果I中有簡單名稱和描述符都符合的方法,則返回此方法的直接引用。
③ 否則,遞歸查找I的父接口和java.lang.Object,如果有簡單名稱和描述符都符合的方法,則返回此方法的直接引用。
④ 否則,宣告查找失敗,拋出java.lang.NoSuchMethodError異常。
五、初始化
初始化的過程就是執(zhí)行類構(gòu)造器<clinit>的過程(如:一些靜態(tài)變量的賦值語句、靜態(tài)代碼塊)。
一些要注意的點:
- 并非所有的類或接口都有<clinit>方法,如果類沒有靜態(tài)變量賦值和靜態(tài)代碼塊、接口沒有靜態(tài)變量賦值,那么編譯器可以不為它們生成<clinit>方法。
- 執(zhí)行類的<clinit>方法不會執(zhí)行它實現(xiàn)接口的<clinit>方法(除非用到了接口的靜態(tài)變量),執(zhí)行接口的<clinit>方法不會執(zhí)行父接口的<clinit>方法(除非用到了父接口的靜態(tài)變量)。
參考資料:
《深入理解JAVA虛擬機》-周志明