一、Class類文件的結(jié)構(gòu)
????????任何一個(gè)Class文件都對(duì)應(yīng)著唯一一個(gè)類或接口的定義信息,但是反過(guò)來(lái)不成立,類或接口并不一定都得定義在文件里(比如類或接口也可以動(dòng)態(tài)生成,直接送入類加載器中)。
? ? ? ? Class文件是一組以8個(gè)字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各數(shù)據(jù)項(xiàng)按順序緊湊的排列,中間沒(méi)有添加任何分隔符,因此Class文件中存儲(chǔ)的內(nèi)容幾乎全是程序運(yùn)行的必要數(shù)據(jù),沒(méi)有空隙存在,當(dāng)遇到需要占用8字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí),按照高位在前的方式分成若干8字節(jié)進(jìn)行存儲(chǔ)。
? ? ? ? Class文件格式采用類似于C語(yǔ)言結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),包含兩種數(shù)據(jù)類型:
? ? ? ? (1)無(wú)符號(hào)數(shù):屬于基本的數(shù)據(jù)類型。u1、u2、u8分別代表1個(gè)字節(jié)、2個(gè)字節(jié)、8個(gè)字節(jié)的無(wú)符號(hào)數(shù),無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或按照UTF-8編碼構(gòu)成的字符串值。
? ? ? ? (2)表:表是有多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表的命名都習(xí)慣以“_info”結(jié)尾。整個(gè)Class文件本質(zhì)上也可以看做一張表,由以下數(shù)據(jù)項(xiàng)按照嚴(yán)格順序排列構(gòu)成:

二、類生命周期
? ? ? ? 虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這個(gè)過(guò)程被稱為虛擬機(jī)的類加載機(jī)制。一個(gè)類型從被加載到虛擬內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,整個(gè)生命周期會(huì)經(jīng)歷七個(gè)階段:

? ? ? ? 其中,加載、驗(yàn)證、準(zhǔn)備、初始化、卸載這五個(gè)階段的順序是肯定的,類加載過(guò)程必須按照這種順序進(jìn)行。而解析則不一定,在某些情況下,解析可以再初始化階段之后開(kāi)始,即動(dòng)態(tài)綁定或晚期綁定。
? ? ? ? 加載過(guò)程中的第一階段,加載,由虛擬機(jī)自行把握時(shí)機(jī)開(kāi)始。但對(duì)于“初始化”,有且只有以下六種情況必須立即對(duì)類進(jìn)行“初始化”:
? ? ? ? (1)遇到new、getstatic、putstatic、invokestatic字節(jié)碼指令時(shí),如果類型沒(méi)有初始化,則需要觸發(fā)初始化;
????????能夠生成上述四條字節(jié)碼指令的場(chǎng)景由:(1)使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候 (2)讀取或設(shè)置一個(gè)類型的靜態(tài)字段時(shí) (3)嗲用一個(gè)類型的靜態(tài)方法時(shí) 。
? ? ? ? (2)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用時(shí),如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)初始化;
? ? ? ? (3)當(dāng)初始化類的時(shí)候,如果發(fā)現(xiàn)父類還沒(méi)有初始化,則需要先觸發(fā)父類的初始化;
? ? ? ? (4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),需要指定一個(gè)主類(main()),虛擬機(jī)會(huì)先初始化這個(gè)主類;
? ? ? ? (5)如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,并且句柄對(duì)應(yīng)的類沒(méi)有初始化過(guò),則需要先觸發(fā)初始化;
? ? ? ? (6)當(dāng)一個(gè)接口定義了默認(rèn)方法(default關(guān)鍵字修飾的接口方法),如果該接口的實(shí)現(xiàn)類發(fā)生了初始化,則該接口要在其之前被初始化。
? ? ? ? 這六種場(chǎng)景,也稱為對(duì)一個(gè)類型的主動(dòng)引用。除此之外的所有引用都不會(huì)觸發(fā)初始化,被稱為被動(dòng)引用。
三、類加載過(guò)程
? ? ? ? 虛擬機(jī)中,類加載過(guò)程分為:加載、驗(yàn)證、準(zhǔn)備、解析、初始化五個(gè)階段。

1. 加載
? ? ? ? 虛擬機(jī)在加載過(guò)程中,主要完成三件事:
? ? ? ? (1)通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流;
? ? ? ? (2)將字節(jié)流代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
? ? ? ? (3)在內(nèi)存中生成一個(gè)代表該類的java.lang.Class對(duì)象,作為方法區(qū)該類的數(shù)據(jù)訪問(wèn)入口。
? ? ? ? 加載階段結(jié)束后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)設(shè)定的格式存儲(chǔ)在方法區(qū)中,并在堆中實(shí)例化一個(gè)Class類的對(duì)象作為程序訪問(wèn)方法區(qū)的外部接口。
2. 驗(yàn)證
? ? ? ? 這一階段是確保Class文件的字節(jié)流中包含的信息符合虛擬機(jī)規(guī)范的全部約束,確保這些信息不會(huì)危害虛擬機(jī)的安全。
? ? ? ? 加載過(guò)程中,并沒(méi)有指明字節(jié)流的來(lái)源必須是Class文件,因此可以通過(guò)鍵盤(pán)輸入等形式產(chǎn)生惡意的字節(jié)流文件,因此驗(yàn)證是必須的,如果不驗(yàn)證檢查輸入的字節(jié)流,很可能造成惡意字節(jié)碼流導(dǎo)致的系統(tǒng)崩潰。
????????驗(yàn)證階段主要包含這四個(gè)階段的檢驗(yàn):文件格式檢驗(yàn)、元數(shù)據(jù)檢驗(yàn)、字節(jié)碼檢驗(yàn)、符號(hào)引用檢驗(yàn):
? ? (1)文件格式檢驗(yàn):是否符合Class文件格式的規(guī)范;
? ? (2)元數(shù)據(jù)檢驗(yàn):對(duì)字節(jié)碼描述信息進(jìn)行語(yǔ)義分析,比如檢驗(yàn)當(dāng)前類是否有父類、當(dāng)前類的父類是否繼承了不允許被繼承的類等;
? ? (3)字節(jié)碼驗(yàn)證:通過(guò)數(shù)據(jù)流分析和控制流分析,檢驗(yàn)程序語(yǔ)義的合法性和符合邏輯;
? ? (4)符號(hào)引用驗(yàn)證:發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候,對(duì)類自身以外的各類信息進(jìn)行匹配性校驗(yàn),校驗(yàn)該類是否缺少或者被進(jìn)制訪問(wèn)它依賴的某些外部類、方法、字段得資源,如果檢驗(yàn)符號(hào)引用中通過(guò)字符串描述的全限定名是否能找到對(duì)應(yīng)的類、符號(hào)引用中的類和字段能否被當(dāng)前類訪問(wèn)等。
驗(yàn)證階段是一個(gè)重要,但非必須的階段,如果在測(cè)試環(huán)境反復(fù)執(zhí)行且沒(méi)有問(wèn)題的項(xiàng)目,可以考慮在生產(chǎn)環(huán)境使用 -X verify:none 來(lái)設(shè)置關(guān)閉大部分的類驗(yàn)證,來(lái)縮短虛擬機(jī)類加載時(shí)間。
3. 準(zhǔn)備
? ? ? ? 準(zhǔn)備階段是正式為類中定義的變量分配內(nèi)存并設(shè)置初始值的階段,這里的初始值通常情況是賦0值的階段。該階段進(jìn)行內(nèi)存分配的僅包含類變量,而不包含實(shí)例變量。
public static int? a = 123 ;
????????在準(zhǔn)備階段過(guò)后,a的初始值為0,而不是123,因?yàn)榇藭r(shí)尚未執(zhí)行Java方法,而把a(bǔ)的值賦為123的指令時(shí)程序被編譯后。
? ? ? ? 除了通常情況外,還有一類特殊情況:
publid static final int a = 123;
? ? ? ? 編譯時(shí)Javac將會(huì)為a生成ConstantValue屬性,在準(zhǔn)備階段直接根據(jù)ConstantValue將a的值設(shè)置為123。
4. 解析
? ? ? ? 解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。
????????符號(hào)引用:用一組符號(hào)來(lái)描述引用的目標(biāo),符號(hào)可以是任何形式的字面量,只有使用時(shí)能無(wú)歧義的定位到目標(biāo)即可。符號(hào)引用的字面量形式明確定義在Class文件格式中。
? ? ? ? 直接引用:直接引用是可以指向目標(biāo)的指針、相對(duì)偏移量或者是能間接定位到目標(biāo)的句柄。
5. 初始化
? ? ? ? 直到最后一個(gè)階段初始化,虛擬機(jī)才真正開(kāi)始執(zhí)行Java程序代碼,將主導(dǎo)權(quán)交給應(yīng)用程序。在準(zhǔn)備階段,變量已經(jīng)賦過(guò)一次初始0值,而在初始化階段,則會(huì)根據(jù)程序編碼制定的主觀計(jì)劃去初始化類變量和其他資源。
四、類加載器
? ? ? ? 把類加載階段“通過(guò)類的全限定名獲取描述該類的二進(jìn)制字節(jié)流”的動(dòng)作放在虛擬機(jī)外部實(shí)現(xiàn),以便讓程序自己決定如何去獲取所需的類,實(shí)現(xiàn)這個(gè)動(dòng)作的代碼被稱為“類加載器”(Class Loader)。
? ? ? ? 對(duì)于每一個(gè)類,都需要由它的類加載器和這個(gè)類本身一起共同確立其在虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。
創(chuàng)建類加載器的語(yǔ)法結(jié)構(gòu)為:
ClassLoader myClassLoader = new ClassLoader() {??
? ??@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {
? ??????????return ;
? ? }
}
? ? ? ? 如果兩個(gè)類由不同的類加載器加載,即便兩個(gè)類來(lái)源于同一個(gè)Class文件,只要類加載器不同,這兩個(gè)類必定不相等。示例代碼:

? ? ? ? 運(yùn)行結(jié)果:
