虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)的類(lèi)加載機(jī)制。
類(lèi)從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段。其中準(zhǔn)備、驗(yàn)證、解析3個(gè)部分統(tǒng)稱(chēng)為連接(Linking)
從ZIP包中讀取,JAR,WAR,EAR格式的基礎(chǔ)
從網(wǎng)絡(luò)中獲取,Applet應(yīng)用
運(yùn)行時(shí)計(jì)算生成,動(dòng)態(tài)代理技術(shù)
由其他文件生成,JSP應(yīng)用,由JSP文件生成對(duì)應(yīng)的Class類(lèi)
驗(yàn)證
驗(yàn)證是連接階段的第一步,確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。驗(yàn)證階段大致分下面4個(gè)動(dòng)作:
文件格式驗(yàn)證
元數(shù)據(jù)驗(yàn)證
字節(jié)碼驗(yàn)證
符號(hào)引用驗(yàn)證
在驗(yàn)證過(guò)程中大致會(huì)拋出如下幾種錯(cuò)誤:
IncompatibleClassChangeError
Unsupported major.minor version
IllegalAccessError
NoSuchFieldError
NoSuchMethodError
文件格式驗(yàn)證
驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。
驗(yàn)證Class文件的標(biāo)識(shí)。魔數(shù)是否以0xCAFEBABE開(kāi)頭
驗(yàn)證Class文件的版本號(hào)。主次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)
驗(yàn)證常量池。常量池的常量中是否有不被支持的常量類(lèi)型
指向常量的各種索引值中是否有指向不存在的常量或不符合類(lèi)型的常量
CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù)
Class文件中各個(gè)部分及文件本身是否有被刪除的或附加的其他信息
通過(guò)了這個(gè)階段的驗(yàn)證后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ)。后面的驗(yàn)證會(huì)基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行驗(yàn)證,而不再操作字節(jié)流進(jìn)行驗(yàn)證。
元數(shù)據(jù)驗(yàn)證
該階段主要對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證符合Java語(yǔ)言規(guī)范要求。
類(lèi)是否有父類(lèi)(除了java.lang.Object外,所有類(lèi)都應(yīng)當(dāng)有父類(lèi))
類(lèi)的父類(lèi)是否繼承了不允許被繼承的類(lèi)(final修飾的類(lèi))
如果類(lèi)不是抽象類(lèi),是否實(shí)現(xiàn)了器父類(lèi)或接口中要求實(shí)現(xiàn)的所有方法
類(lèi)中的字段,方法是否與父類(lèi)產(chǎn)生矛盾(如:覆蓋了父類(lèi)的final字段)
字節(jié)碼驗(yàn)證
該階段主要是通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的。
對(duì)類(lèi)的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類(lèi)的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的事件。
符號(hào)引用驗(yàn)證
符號(hào)引用驗(yàn)證是對(duì)類(lèi)自身以外(常量池中的各種符號(hào)引用)的信息進(jìn)行匹配性校驗(yàn),該階段在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用時(shí)發(fā)生。
符號(hào)引用中通過(guò)字符串描述的全限定名是否能找到對(duì)應(yīng)的類(lèi)。
在指定類(lèi)中是否存在符合方法的字段描述以及簡(jiǎn)單名稱(chēng)所描述的方法和字段。
符號(hào)引用中的類(lèi),字段,方法的訪(fǎng)問(wèn)性(private,protected,public,default)是否可被當(dāng)前類(lèi)訪(fǎng)問(wèn)。
符號(hào)引用驗(yàn)證的目的就是確保解析動(dòng)作能正常執(zhí)行。
準(zhǔn)備
準(zhǔn)備階段是為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始化的階段,這些變量所使用的內(nèi)存當(dāng)將在方法區(qū)中進(jìn)行分
配。只對(duì)類(lèi)變量進(jìn)行內(nèi)存分配(static修飾),不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化是隨著對(duì)象一起分配在Java堆中。
如:一個(gè)類(lèi)變量的定義為
// n的初始化值是0,而不是2。因?yàn)檫@個(gè)時(shí)候還沒(méi)執(zhí)行任何初始化方法(<clinit>)。 public static int n = 2;
再例如:
// 編譯時(shí)會(huì)為m生成ConstantValue屬性,在準(zhǔn)備階段會(huì)根據(jù)ConstantValue將m值設(shè)置為2 public static final int m = 2;
類(lèi)變量和實(shí)例變量
類(lèi)變量:也稱(chēng)為靜態(tài)變量,在類(lèi)中以static關(guān)鍵字聲明,但必須在方法構(gòu)造方法和語(yǔ)句塊之外
實(shí)例變量:屬于該類(lèi)的對(duì)象,必須產(chǎn)生該類(lèi)對(duì)象,才能調(diào)用實(shí)例變量。
解析
解析的目的就是將常量池內(nèi)的符號(hào)引用替換為直接引用。
符號(hào)引用(Symbolic References):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。符號(hào)引用的字面量形式已經(jīng)明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。
直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān),同一個(gè)符號(hào)引用在不用虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
一句話(huà)總結(jié):符號(hào)引用是以字面量的形式明確定義在常量池中;直接引用是指向目標(biāo)的指針,或者相對(duì)偏移量。
解析動(dòng)作主要對(duì)類(lèi)或者接口、字段、類(lèi)方法、接口方法、方法類(lèi)型、方法句柄和調(diào)用點(diǎn)限定符7類(lèi)符號(hào)引用進(jìn)行,分別對(duì)應(yīng)于常量池
CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
CONSTANT_InterfaceMethodref_info
CONSTANT_MethodType_info
CONSTANT_Methodref_info
CONSTANT_MethodHandler_info
CONSTANT_invokeDynamic_info
字段的解析
class A extends B implements C, D{
private String str; //字段的解析
}
解析字段的順序:
①先查找本類(lèi)A,如果包含了簡(jiǎn)單名稱(chēng)和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束。
②否則,在接口中查找。將會(huì)按照集成關(guān)系從下往上遞歸搜搜各個(gè)接口和它的父接口,如果接口中包含了簡(jiǎn)單名稱(chēng)和字段描述符都于目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束。
③否則,在父類(lèi)中查找,如果在父類(lèi)中包含了簡(jiǎn)單名稱(chēng)和字段描述符都于目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束
④否則,查找失敗,拋出java.lang.NoSuchFieldError異常。
類(lèi)方法的解析
class A extends B implements C, D{
private void inc(); //方法的解析
}
①如果在類(lèi)方法表中發(fā)現(xiàn)class_index中索引的A是一個(gè)接口,哪就直接拋出java.lang.IncompatiableClassChangeError異常。
②如果通過(guò)了第一步,先查找本類(lèi)A,是否由簡(jiǎn)單名稱(chēng)和描述符都于目標(biāo)相匹配的方法,如果有則返回方法的直接引用,查找結(jié)束。
③否則,父親中遞歸查找是否又簡(jiǎn)單名稱(chēng)和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
④否則,在類(lèi)實(shí)現(xiàn)的接口列表及它們的父接口之中查找是否有簡(jiǎn)單名名稱(chēng)和描述符都與目標(biāo)相匹配的方法,如果存在匹配的方法,說(shuō)明類(lèi)C是一個(gè)抽象類(lèi),這時(shí)查找結(jié)束,拋出java.lang.AbastractMethodError異常。
⑤否則,宣告方法查找失敗,拋出java.lang.NoSuchMethodError。
接口方法的解析
與類(lèi)的方法解析不同,如果在接口方法表中發(fā)現(xiàn)class_index中的索引A是個(gè)類(lèi)而不是接口,那就直接拋出java.lang.IncompatiableClassError異常。
否則,先查找本接口,是否有簡(jiǎn)單名稱(chēng)和描述符都與目標(biāo)匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
否則,在接口的父接口中遞歸查找,直到j(luò)ava.lang.Object類(lèi)(查找范圍包括Object類(lèi))為止,看是否有簡(jiǎn)單名稱(chēng)和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
否則,宣告方法查找失敗,拋出java.lang.NoSuchMethodError異常。
初始化
<clinit> 類(lèi)的初始化。靜態(tài)變量,靜態(tài)塊的初始化。所有的類(lèi)變量初始化語(yǔ)句和類(lèi)型的靜態(tài)初始化器。
Java在編譯之后會(huì)在字節(jié)碼文件中生成<clinit>方法,稱(chēng)之為類(lèi)構(gòu)造器,類(lèi)構(gòu)造器同實(shí)例構(gòu)造器一樣,也會(huì)對(duì)靜態(tài)語(yǔ)句塊,靜態(tài)變量進(jìn)行初始化
<init> 對(duì)象的初始化
Java在編譯之后會(huì)在字節(jié)碼文件中生成<init>方法,稱(chēng)之為實(shí)例構(gòu)造器。該實(shí)例構(gòu)造器會(huì)對(duì)語(yǔ)句塊,變量進(jìn)行初始化,并調(diào)用父類(lèi)的構(gòu)造器。
<clinit>方法是在類(lèi)加載過(guò)程中執(zhí)行的,而<init>是在對(duì)象實(shí)例化執(zhí)行的,所以<clinit>一定比<init>先執(zhí)行。所以整個(gè)順序就是:
父類(lèi)靜態(tài)變量初始化
父類(lèi)靜態(tài)語(yǔ)句塊
子類(lèi)靜態(tài)變量初始化
子類(lèi)靜態(tài)語(yǔ)句塊
父類(lèi)變量初始化
父類(lèi)語(yǔ)句塊
父類(lèi)構(gòu)造函數(shù)
子類(lèi)變量初始化
子類(lèi)語(yǔ)句塊
子類(lèi)構(gòu)造函數(shù)