Java虚拟机类加载机制
虛擬機(jī)類加載機(jī)制:虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型。
Java語言里,類型的加載和連接過程是在程序運(yùn)行期間完成的。
?
類的生命周期:
加載 loading
驗(yàn)證 verification
準(zhǔn)備 preparation
解析 resolution
初始化 initialization
使用 using
卸載 unloading
?
有且只有以下四種情況必須立即對(duì)類進(jìn)行”初始化”(稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用):
- 遇到new、getstatic、putstatic、invokestatic這四條字節(jié)碼指令時(shí)(使用new實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段、調(diào)用一個(gè)類的靜態(tài)方法)。
- 使用java.lang.reflet包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候。
- 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其負(fù)類沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
- 當(dāng)虛擬機(jī)啟動(dòng)時(shí),虛擬機(jī)會(huì)初始化主類(包含main方法的那個(gè)類)。
?
被動(dòng)引用:
- 通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化(對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化)。
- 通過數(shù)組定義類應(yīng)用類:ClassA [] array=new ClassA[10]。觸發(fā)了一個(gè)名為[LClassA的類的初始化,它是一個(gè)由虛擬機(jī)自動(dòng)生成的、直接繼承于Object的類,創(chuàng)建動(dòng)作由字節(jié)碼指令newarray觸發(fā)。
- 常量會(huì)在編譯階段存入調(diào)用類的常量池。
?
編譯器會(huì)為接口生成<clinit>()構(gòu)造器,用于初始化接口中定義的成員變量。一個(gè)接口在初始化時(shí),并不要求其父類接口全部完成了初始化,只有在真正使用到父接口的時(shí)候才會(huì)初始化。
?
1.????? 加載
- 通過一個(gè)類的全限定名來獲取此類的二進(jìn)制字節(jié)流。
- 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
- 在java堆中生成一個(gè)代表這個(gè)類的Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。
?
?
2.????? 驗(yàn)證
驗(yàn)證:確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
虛擬機(jī)規(guī)范:如果驗(yàn)證到輸入的字節(jié)流不符合Class文件的存儲(chǔ)格式,就拋出一個(gè)java.lang.VerifyError異常或其子類異常。
?
- 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。這個(gè)階段的驗(yàn)證時(shí)給予字節(jié)流進(jìn)行的,經(jīng)過了這個(gè)階段的驗(yàn)證之后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ)所以后面的驗(yàn)證階段都是給予方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的。
- 元數(shù)據(jù)驗(yàn)證:對(duì)類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn),保證不存在不符合java語言規(guī)范的元數(shù)據(jù)信息。
- 字節(jié)碼驗(yàn)證:進(jìn)行數(shù)據(jù)流和控制流分析,對(duì)類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)的類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。
- 符號(hào)引用驗(yàn)證:發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候(解析階段),對(duì)常量池中的各種符號(hào)引用的信息進(jìn)行匹配性的校驗(yàn)。
?
3.????? 準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值(各數(shù)據(jù)類型的零值)的階段,這些內(nèi)存將在方法區(qū)中進(jìn)行分配。但是如果類字段的字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量值就會(huì)初始化為ConstantValue屬性指定的值。
public static final int value=122;
?
4.????? 解析
解析階段是在虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。
?
符號(hào)引用:符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
?
直接引用:直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或者一個(gè)能間接定位到目標(biāo)的句柄。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
?
A.??? 類或接口(對(duì)應(yīng)于常量池的CONSTANT_Class_info類型)的解析:
假設(shè)當(dāng)前代碼所處的類為D,需要將一個(gè)從未解析過的符號(hào)引用N解析為一個(gè)類或接口C的直接引用:
- 如果C不是一個(gè)數(shù)組類型,虛擬機(jī)將會(huì)把代表C的全限定名傳遞給D的類加載器去加載這個(gè)類。
- 如果C是一個(gè)數(shù)組類型,并且數(shù)組的元素類型為對(duì)象(N的描述符類似[Ljava.lang.Integer),將會(huì)加載數(shù)組元素類型(java.lang.Integer),接著由虛擬機(jī)生成一個(gè)代表此數(shù)組維度和元素的數(shù)組對(duì)象。
- 如果以上過程沒有發(fā)生異常,則C在虛擬機(jī)中已經(jīng)成為了一個(gè)有效的類和接口了,之后還要進(jìn)行的是符號(hào)引用驗(yàn)證,確認(rèn)D是否具有對(duì)C的訪問權(quán)限,如果沒有,將拋出java.lang.IllegalAccessError異常。
?
B.???? 字段(對(duì)應(yīng)于常量池的CONSTANT_Fieldref_info類型)解析:
- 對(duì)字段表中的class_index項(xiàng)中索引的CONSTANT_Class_info符號(hào)引用進(jìn)行解析。用C表示這個(gè)字段所屬的類或接口。
- 如果C本身就包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用。
- 否則,如果C實(shí)現(xiàn)了接口,則會(huì)按照繼承關(guān)系從下往上遞歸搜索各個(gè)接口和他的父接口,如果接口中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用。
- 否則,如果C不是java.lang.Object類型的話,將會(huì)按照繼承關(guān)系從下往上遞歸的搜索其父類,如果在父類中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用。
- 否則,查找失敗,拋出java.lang.NoSuchFieldError異常。
虛擬機(jī)的編譯器實(shí)現(xiàn)可能會(huì)更嚴(yán)格:如果一個(gè)同名字段同時(shí)出現(xiàn)在C實(shí)現(xiàn)的接口和父類中,或者同時(shí)在自己或父類的多個(gè)接口中出現(xiàn),編譯器將可能拒絕編譯。
?
C.???? 類方法(對(duì)應(yīng)于常量池的CONSTANT_Methodref_info類型)解析:
- 對(duì)方法表中的class_index項(xiàng)中索引的CONSTANT_Class_info符號(hào)引用進(jìn)行解析。用C表示這個(gè)方法所屬的類或接口。
- 類方法和接口方法符號(hào)引用的常量類型定義是分開的,如果在類方法表中發(fā)現(xiàn)class_index中索引的C是個(gè)接口,則拋出java.lang.IncompatibleClassChangeError。
- 在類C中查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用。
- 否則,在C的父類中遞歸查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用。
- 否則,在C實(shí)現(xiàn)的接口列表及它們的父接口中遞歸的查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有說明C是個(gè)抽象類,查找結(jié)束,拋出java.lang.AbstractMethodError異常。
- 否則,查找失敗,拋出java.lang.NoSuchMethodError異常。
- 如果查找返回了直接引用,將會(huì)對(duì)這個(gè)方法進(jìn)行權(quán)限驗(yàn)證,如果發(fā)現(xiàn)不具備對(duì)這個(gè)方法的訪問權(quán)限,則拋出java.lang.IllegalAccessError異常。
?
D.??? 接口方法(對(duì)應(yīng)于常量池的CONSTANT_InterfaceMethodref_info類型):
- 對(duì)方法表中的class_index項(xiàng)中索引的CONSTANT_Class_info符號(hào)引用進(jìn)行解析。用C表示這個(gè)方法所屬的類或接口。
- 如果在接口方法表中發(fā)現(xiàn)class_index中索引的C是個(gè)類,則拋出java.lang.IncompatibleClassChangeError。
- 否則,在接口C中查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用。
- 否則,在接口C的父接口中遞歸查找,知道java.lang.Object類(包括在內(nèi)),看是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用。
- 否則,查找失敗,拋出java.lang.NoSuchMethodError。
?
5.????? 初始化
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
?
- <clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的。靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句塊中可以賦值,但是不能訪問。
?
2. 方法與實(shí)例構(gòu)造器<init>()不同,不需要顯示的調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()已經(jīng)執(zhí)行完畢。
?
3.?<clinit>()方法對(duì)于類或接口來說不是必須的,如果一個(gè)類中沒有靜態(tài)語句塊也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成<clinit>()方法。
?
4.?執(zhí)行接口的<clinit>()不需要先執(zhí)行父接口的<clinit>()方法,只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的<clinit>()方法。
?
? 5.?虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確的加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,則只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其他線程需要阻塞等待。
轉(zhuǎn)載于:https://www.cnblogs.com/moonandstar08/p/4876035.html
總結(jié)
以上是生活随笔為你收集整理的Java虚拟机类加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 现在的铜价是多少钱一斤?
- 下一篇: IOS中打开应用实现检查更新的功能