第七章 虛擬機(jī)類加載機(jī)制
7.1 類加載特性
與那些在編譯時(shí)需要進(jìn)行連接工作的語言不同,在JAVA中,類型的加載和連接過程都是在程序運(yùn)行期間完成的羹奉,這樣會(huì)在類加載時(shí)稍微增加一些性能開銷唠倦,但是卻能為JAVA應(yīng)用程序提高高度的靈活性厂镇,JAVA中天生可以動(dòng)態(tài)擴(kuò)展的語言特性就是依賴運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)連接這個(gè)特點(diǎn)實(shí)現(xiàn)的。
7.2 類加載過程
加載(Loading)->驗(yàn)證(Verification)->準(zhǔn)備(Preparation)->解析(Resolution)->初始化(Initialization)->使用(Using)->卸載(Unloading)
** 其中驗(yàn)證雅镊,準(zhǔn)備襟雷,解析這三個(gè)過程稱為連接 **
** 加載、驗(yàn)證仁烹、準(zhǔn)備耸弄、初始化和卸載這5個(gè)階段的順序是確定的 **
7.3 加載(Loading)過程
加載過程以及完成之后要做的兩件事情:
1.通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
where&how獲取二進(jìn)制流?
- 從JAR卓缰、EAR计呈、WAR格式的包中(Tomcat就是通過部署在上邊的WAR包來運(yùn)行整個(gè)web項(xiàng)目的)
- 從網(wǎng)絡(luò)中獲取(Applet應(yīng)用)
- 動(dòng)態(tài)代理技術(shù)(java.lang.reflect.Proxy)
- JSP文件
- 從數(shù)據(jù)庫中讀取(中間件服務(wù)器SAP Netweaver)
2.將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu):即java.lang.Class對(duì)象
7.4 驗(yàn)證(Verification)過程
7.4.1 目的:為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全征唬。(WHY?因?yàn)镃lass文件不僅僅可以由java源文件編譯形成捌显,也可以人為構(gòu)造Class文件。如果輸入的字節(jié)流不符合Class文件的存儲(chǔ)格式总寒,拋錯(cuò):java.lang.VerifyError)
7.4.2 四大驗(yàn)證步驟:
- 文件格式驗(yàn)證:(來源于HotSpot虛擬機(jī)標(biāo)準(zhǔn))
1.1 是否以魔數(shù)0xCAFEBABE開頭
1.2 主扶歪、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)
1.3 常量池的常量中是否有不被支持的常量類型
1.4 指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量
1.5 CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù)
1.6 Class文件中各個(gè)部分以及文件本身是否有被刪除的或附加的其他信息
1.7 more...... - 元數(shù)據(jù)驗(yàn)證
作用:語義驗(yàn)證,保證不存在不符合java語言規(guī)范的元數(shù)據(jù)信息
通常驗(yàn)證以下幾項(xiàng)內(nèi)容:
2.1 這個(gè)類是否有父親(除了java.lang.Obejct之外偿乖,所有的類都應(yīng)當(dāng)有父類)
2.2 這個(gè)類的父親是否繼承了不允許被繼承的類(被final修飾的類)
2.3 如果這個(gè)類不是抽象類击罪,是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法
2.4 類中的字段、方法是否與父類產(chǎn)生了矛盾
2.5 more...... - 字節(jié)碼驗(yàn)證
作用:對(duì)類的方法體進(jìn)行校驗(yàn)分析贪薪,確保被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為
3.1 保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼都能配合工作媳禁,例如不會(huì)出現(xiàn)類似這樣的情況:在操作棧中放置了一個(gè)int類型的數(shù)據(jù),使用的時(shí)候卻按long類型來加載到本地變量表中
3.2 保證跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
3.3 保證方法體中的類型轉(zhuǎn)換是有效的画切,例如可以把一個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型竣稽,這是安全的,反之則是危險(xiǎn)的。
3.4 more......
** 注意:如果一個(gè)方法體通過了字節(jié)碼驗(yàn)證毫别,也不能說明其一定就是安全的娃弓。(Halting Problem停機(jī)問題。) ** - 符號(hào)引用驗(yàn)證
作用:將符號(hào)引用轉(zhuǎn)化為直接引用岛宦,在"解析階段"中發(fā)生台丛。如無法通過驗(yàn)證,則拋錯(cuò):IncompatibleClassChangeError異常的子類,如java.lang.IllegalAccessError砾肺、java.lang.NoSuchFieldError挽霉、java.lang.NoSuchMethodError等
4.1 符號(hào)引用中通過字符串描述的全限定名是否能找到對(duì)應(yīng)的類
4.2 在指定類中是否存在符合方法的字段描述符以及簡(jiǎn)單名稱所描述的方法和字段
4.3 符號(hào)引用中的類、字段和方法的訪問行(private,protected,public,default)是否可被當(dāng)前類訪問
4.4 more......
7.5 準(zhǔn)備(Preparation)過程
1.作用:正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段变汪,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配
2.為類變量分配是指:被static修飾的變量侠坎,不包括實(shí)例變量。實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在JAVA堆中裙盾。
3.這里的初始化是指"通常情況"下數(shù)據(jù)類型的零值
例子:public static int value = 123;
這里的value變量实胸,在準(zhǔn)備階段過后的初始值為0,而不是123番官,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何JAVA方法庐完,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構(gòu)造器<clinit>()方法之中鲤拿,所以value = 123的動(dòng)作將在初始化階段才會(huì)被執(zhí)行
4.除了上邊提到的數(shù)據(jù)初始化的"通常情況"假褪,還有"特殊情況":
public static final int value = 123;
這個(gè)時(shí)候類字段的字段屬性就有了ConstantValue屬性,那在準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值
數(shù)據(jù)類型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
7.6 解析(Resolution)過程
作用:虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程近顷。
** 那么生音,什么是符號(hào)引用和直接引用呢? **
- 符號(hào)引用(Symbolic References):符號(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)存中域醇。
- 直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄蓉媳。直接引用是虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的譬挚,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般不會(huì)相同。如果有了直接引用酪呻,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在减宣。
** 那么,什么時(shí)候解析呢玩荠?**
虛擬機(jī)規(guī)范并未規(guī)定解析階段發(fā)生的具體時(shí)間漆腌,只要求了在執(zhí)行anewarray贼邓、checkcast、getfield闷尿、getstatic塑径、instanceof、invokeinterface填具、invokespecial统舀、invokestatic、invokevirtual劳景、multianewarray绑咱、new、putfield枢泰、putstatic這13個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前。
** 針對(duì)的對(duì)象是什么呢铝噩? **
解析動(dòng)作主要針對(duì)類或接口衡蚂、字段、類方法骏庸、接口方法四類符號(hào)引用進(jìn)行毛甲,分別對(duì)應(yīng)于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info具被、CONSTANT_Methodref_info玻募、CONSTANT_InterfaceMethodref_info四種常量類型
7.7 初始化(Initialization)過程
該過程是類加載過程的最后一步。在該過程中一姿,才開始真正執(zhí)行類中定義的Java代碼七咧。(注意該過程并不是對(duì)象的實(shí)例化
)
虛擬機(jī)會(huì)執(zhí)行類構(gòu)造器<clinit>()方法
來初始化該類。簡(jiǎn)單歸納一下就是:
- 類的初始化:(最后一步)執(zhí)行面向類的構(gòu)造器(<clinit>())
- 對(duì)象的初始化:執(zhí)行面向?qū)ο蟮臉?gòu)造器(Constructor)
回歸正題:
類構(gòu)造器<clinit>()方法
是指:該方法是由編譯器自動(dòng)收集類中的所有類變量
的賦值動(dòng)作
和靜態(tài)語句塊中
的語句
合并產(chǎn)生的叮叹。編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的艾栋。
注意:靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量蛉顽,在前面的靜態(tài)語句塊中可以賦值
蝗砾,但是不能訪問
更多詳細(xì)的細(xì)節(jié)見《深入理解Java虛擬機(jī)》7.3.5章節(jié)