類的生命周期
加載——>驗證——>準備——>解析——>初始化——>使用——>卸載
驗證、準備、解析又統(tǒng)稱連接階段。
其中加載、驗證、準備、初始化和卸載5個階段的順序是確定的,類的加載過程必須按照這種順序按部就班的開始。要注意的是,這里這是單純的開始,開始并不是指進行或者完成,這些階段通常都是互相交叉地混合進行的,通常會在一個階段執(zhí)行的過程中調(diào)用激活另外一個階段。
解析階段則不一定:它在某些情況下可以在初始化階段之后再開始。
那什么時候開始加載一個類呢?如果以下情況發(fā)生時,發(fā)現(xiàn)類還未被初始化,則必須要對類進行初始化(順帶著肯定有加載驗證準備階段的開始):
- new一個實例時,和讀取設(shè)置一個類的靜態(tài)字段或者調(diào)用類的靜態(tài)方法時。
- 反射調(diào)用這個類的時候
- 初始化一個類時,發(fā)現(xiàn)其父類還未初始化,就會先觸發(fā)父類的初始化
- 當(dāng)虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類,虛擬機會先初始化這個主類。
- 當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getstatic、REF_putStatic、REF_invokeStatic的句柄對應(yīng)的類還未初始化時
加載
主要完成3件事:
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
加載階段是開發(fā)人員可控性最強的,開發(fā)人員可以通過自定義的類加載器去控制字節(jié)流的獲取方式。
加載階段和連接階段的部分內(nèi)容是交叉進行的,加載階段尚未完成,連接階段可能已經(jīng)開始。
驗證
確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全。
- 文件格式驗證
- 元數(shù)據(jù)驗證
- 字節(jié)碼驗證
- 符號引用驗證
準備
準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。
解析
解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。
符號引用: 符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān),引用的目標并不一定已經(jīng)加載到內(nèi)存中。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規(guī)范的Class格式中。
直接引用: 直接引用可以是直接指向目標的指針、相對偏移量或者一個能間接定位到目標的句柄。直接引用是和虛擬機實現(xiàn)的內(nèi)存布局相關(guān)的,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,那引用的目標必定已經(jīng)在內(nèi)存中存在。
初始化
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
- <clinit>()方法是編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生的。
- 虛擬機必須保證在子類的<clinit>()方法執(zhí)行前,父類的<clinit>()方法必須已經(jīng)執(zhí)行完。
- 如果一個類中沒有靜態(tài)語句塊,也沒有對變量的賦值操作,那么編譯器就不會為這個類生成<clinit>()方法。
- 接口也有<clinit>()方法。但是和類不同,執(zhí)行接口的<clinit>()方法時,不需要執(zhí)行父接口的<clinit>()方法。只要當(dāng)父接口中定義的變量使用時,父接口才會初始化。
- 虛擬機會保證<clinit>()方法是線程同步的。
類加載器
對于任何一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器都擁有一個獨立的類名稱空間。
也就是說,如果兩個類來源于同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。這里的相等包括equals()方法、isAssignableFrom()方法、isInstance()方法,也包括使用intanceof關(guān)鍵字的情況。
雙親委派模型
雙親委派模型對于保證java程序的穩(wěn)定運作很重要。
雙親委派模型的工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋自己無法完成這個加載請求時,子加載器才會嘗試自己去加載。
絕大部分Java程序都會使用到以下3種系統(tǒng)提供的類加載:
- 啟動類加載器(Bootstrap ClassLoader):負責(zé)將存放在<JAVA_HOME>\lib目錄中的類庫加載到虛擬機內(nèi)存中。
- 擴展類加載器(Extension ClassLoader): 他負責(zé)加載<JAVA_HOME>\lib\ext目錄或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用擴展類加載器。
- 應(yīng)用程序類加載器(Application ClassLoader): 它負責(zé)加載用戶類路徑上鎖指定的類庫,開發(fā)者可以直接使用這個類加載器。