類加載的時機:
1.遇到new、getstatic骄呼、putstatic或invokestatic這4條字節(jié)碼指令時惰拱,如果類沒有進行過初始化,則需要先觸發(fā)其初始化凛虽。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實例化對象的時候死遭、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候凯旋,以及調(diào)用一個類的靜態(tài)方法的時候呀潭。
2.使用java.lang.reflect包的方法對類進行反射調(diào)用的時候钉迷,如果類沒有進行過初始化,則需要先觸發(fā)其初始化蜗侈。
3.當(dāng)初始化一個類的時候篷牌,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化踏幻。
4.當(dāng)虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類)戳杀,虛擬機會先初始化這個類该面。
5.當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic信卡、REF_invokeStatic的方法句柄隔缀,并且這個方法句柄所對應(yīng)的類沒有進行過初始化,則需要先觸發(fā)其初始化傍菇。
類加載的過程:
1.加載:在加載階段猾瘸,虛擬機需要完成以下三件事情:
1)通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。
2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)丢习。
3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象牵触,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
從上述要求來說咐低,加載這一過程的要求相對寬松揽思,因此虛擬機實現(xiàn)與具體應(yīng)用的靈活度都是相當(dāng)大的。例如“通過一個類的全限定名來獲取定義此類的二進制字節(jié)流”這條见擦,它沒有指明二進制字節(jié)流要從一個Class文件中獲取钉汗,準(zhǔn)確地說是根本沒有指明要從哪里獲取、怎樣獲取鲤屡。虛擬機設(shè)計團隊在加載階段搭建了一個相當(dāng)開放的损痰、廣闊的“舞臺”,java發(fā)展歷程中酒来,充滿創(chuàng)造力的開發(fā)人員則在這個“舞臺”上玩出了各種花樣卢未,許多舉足輕重的Java技術(shù)都建立在這一基礎(chǔ)之上。
加載階段完成后役首,虛擬機外部的二進制字節(jié)流就按照虛擬機所需的格式存儲在方法區(qū)之中尝丐,方法區(qū)中的數(shù)據(jù)存儲格式由虛擬機實現(xiàn)自行定義,虛擬機規(guī)范未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)衡奥。然后在內(nèi)存中實例化一個java.lang.Class類的對象(并沒有明確規(guī)定是在Java堆中爹袁,對于HotSpot虛擬機而言,Class對象比較特殊矮固,它雖然是對象失息,但是存放在方法區(qū)里面)譬淳,這個對象將作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口。
2.驗證:驗證是連接階段的第一步盹兢,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求邻梆,并且不會危害虛擬機自身的安全。
Java語言本身是相對安全的語言绎秒,使用純粹的Java代碼無法做到諸如訪問數(shù)組邊界以外的數(shù)據(jù)浦妄、將一個對象轉(zhuǎn)型為它并為實現(xiàn)的類型,如果這樣做編譯器將拒絕執(zhí)行见芹,但前面已經(jīng)說過剂娄,Class文件并不一定要求用Java源碼編譯而來,可以使用任何途徑產(chǎn)生玄呛,甚至包括用十六進制編輯器直接編寫來產(chǎn)生Class文件阅懦。在字節(jié)碼層面上,上述Java代碼無法做到的事情都是可以實現(xiàn)的徘铝,至少語義上是可以表達(dá)出來的耳胎。
驗證階段的工作量在虛擬機的類加載子系統(tǒng)中非常重要,又占了相當(dāng)大的一部分惕它。
1.文件格式驗證:
? a.是否以魔數(shù)0xCAFEBABE開頭怕午;? ? ? ??
? b.主、次版本號是否在當(dāng)前虛擬機處理范圍之內(nèi)怠缸;??
? c.常量池的常量中是否有不被支持的常量類型诗轻;
2.元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析。
? ?a.這個類是否有父類揭北;
? ?b.這個類的父類是否繼承了不允許被繼承的類扳炬;
3.字節(jié)碼驗證:主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的搔体、符合邏輯的恨樟。將對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會危害虛擬機疚俱。
? ? a.保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作劝术。
? ? b.保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上。
4.符號引用驗證:對類自身以外的信息進行匹配性校驗呆奕。
3.準(zhǔn)備:準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段养晋,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。
? ? ?這時候進行內(nèi)存分配的僅包括類變量(被static修飾的變量)梁钾,而不包括實例變量绳泉,實例變量將會在對象實例化時隨著對象一起分配在Java堆中,其次姆泻,這里所說的初始值“通常情況”下是數(shù)據(jù)類型的零值零酪。
4.解析:該階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程
符號引用:符號引用以一組符號來描述所飲用的目標(biāo)冒嫡,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標(biāo)即可四苇。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān)孝凌,引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同月腋,但是他們能接受的符號引用必須都是一致的蟀架,因為符號引用的字面量形式明確定義在Java虛擬機規(guī)范的Class文件格式中。
直接引用:直接引用可以是直接指向目標(biāo)的指針罗售、相對偏移量或是一個能間接定位到目標(biāo)的句柄辜窑。直接引用是和虛擬機實現(xiàn)的內(nèi)存布局相關(guān)的,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同寨躁。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在牙勘。
解析動作主要針對類或接口职恳、字段、類方法方面、接口方法放钦、方法句柄和調(diào)用點限定符。
5.初始化:前面的類加載過程中恭金,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外操禀,其余動作都是由虛擬機主導(dǎo)和控制。到了初始化階段横腿,才真正開始執(zhí)行類中定義的Java程序代碼颓屑。
? ? 在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值耿焊,而在初始化階段揪惦,則根據(jù)程序員通過程序定制的主觀計劃去初始化類變量和其他資源,或者可以從另外一個角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程罗侯。