上篇文章提到過,類加載一共七步驟:加載麦乞、驗(yàn)證蕴茴、準(zhǔn)備、解析姐直、初始化倦淀、使用、卸載∩罚現(xiàn)在講解前五步驟撞叽。
一姻成、加載
在類加載階段,虛擬機(jī)需要完成以下三件事:
1)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)對這個(gè)類的各種數(shù)據(jù)的訪問入口
總結(jié):查找并加載二進(jìn)制字節(jié)碼到方法區(qū)愿棋,然后在內(nèi)存中實(shí)例化一個(gè)java.lang.Class類的對象
二科展、驗(yàn)證
1、文件格式驗(yàn)證
1)是否以魔數(shù)0xCAFEBABE開頭
2)主次版本號是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)
3)常量池的常量中是否有不被支持的常量類型
4)是否有指向不存在的常量或不符合類型的常量
5)CONSTANT_Utf8_info型的長兩種是否有不符合UTF8編碼的數(shù)據(jù)
6)Class文件中各個(gè)部分及文件本身是否又被刪除的或附加的其他信息
...
該階段的主要目的是保證輸入的字節(jié)流能正確的解析并存儲(chǔ)于方法區(qū)之內(nèi)
2糠雨、元數(shù)據(jù)驗(yàn)證
1)這個(gè)類是否有父類(除了java.lang.Object之外才睹,其他所有的類都應(yīng)該有父類)
2)這個(gè)類的父類是否繼承了不允許被繼承的類(被final修飾的類)
3)如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法
4)類的字段甘邀,方法是否與父類產(chǎn)生矛盾(例如覆蓋了父類的final字段琅攘,或者出現(xiàn)不符合規(guī)則的方法重載,比如方法參數(shù)都一樣鹃答,但返回值卻不同乎澄。)
...
該階段主要目的是對類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn),保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息测摔。
3置济、字節(jié)碼驗(yàn)證
1)保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作,例如不會(huì)出現(xiàn):在操作棧放置了一個(gè)int類型的數(shù)據(jù)锋八,使用時(shí)卻按long類型來加載入本地變量表中
2)保證跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
...
該階段主要目的是通過數(shù)據(jù)流和控制流分析浙于,確定程序語義是合法的、符合邏輯的挟纱。
4羞酗、符號引用驗(yàn)證
1)符號引用中通過字符串描述的全限定名是否能找到對應(yīng)的類
2)在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
3)符號引用中的類、字段紊服、方法的訪問性是否可以被當(dāng)前類訪問
...
該階段的目的是確保解析動(dòng)作能正常執(zhí)行檀轨,否則將會(huì)拋出一個(gè)java.lang.IncompatibleClasschangeError異常的子類
驗(yàn)證階段是非常重要的,但不是必須的欺嗤,它對程序運(yùn)行期沒有影響参萄,如果所引用的類經(jīng)過反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來關(guān)閉大部分的類驗(yàn)證措施煎饼,以縮短虛擬機(jī)類加載的時(shí)間讹挎。
三、準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段吆玖,這些內(nèi)存都將在方法區(qū)中分配筒溃。
1)這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量沾乘,實(shí)例變量會(huì)在對象實(shí)例化時(shí)隨著對象一塊分配在Java堆中怜奖。
2)這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L翅阵、null烦周、false等)尽爆,而不是被在Java代碼中被顯示的賦予的值。
假設(shè)一個(gè)類變量的定義為:
public static int value = 3;
那么變量value在準(zhǔn)備階段過后的初始值為0读慎,而不是3漱贱,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后夭委,存放于類構(gòu)造器<clinit>()方法之中的幅狮,所以把value賦值為3的動(dòng)作將在初始化階段才會(huì)執(zhí)行。
3)如果類字段的字段屬性表中存在ConstantValue屬性株灸,即同時(shí)被static 和 final修飾崇摄,那么在準(zhǔn)備階段變量value就會(huì)會(huì)初始化為ConstantValue屬性所指定的值。
假設(shè)上面的變量value被定為:
public static final int value = 3;
編譯時(shí)Javac將會(huì)為value生成ConstantValue屬性慌烧,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value賦值為3逐抑。
總結(jié):為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值
四屹蚊、解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程厕氨,解析動(dòng)作主要針對類或接口、字段汹粤、類方法命斧、接口方法、方法類型嘱兼、方法句柄和調(diào)用點(diǎn)限定符7類符號引用進(jìn)行国葬。符號引用就是一組符號來描述目標(biāo),可以是任何字面量芹壕。
直接引用就是直接指向目標(biāo)的指針汇四、相對偏移量或一個(gè)間接定位到目標(biāo)的句柄。
總結(jié):把類中的符號引用轉(zhuǎn)換為直接引用
五踢涌、初始化
初始化通孽,為類的靜態(tài)變量賦予正確的初始值,JVM負(fù)責(zé)對類進(jìn)行初始化斯嚎,主要對類變量進(jìn)行初始化。在Java中對類變量進(jìn)行初始值設(shè)定有兩種方式:
①聲明類變量是指定初始值
②使用靜態(tài)代碼塊為類變量指定初始值
初始化也是執(zhí)行類構(gòu)造器<clinit>()方法的過程堡僻。
1)<clinit>()方法是由編譯器自動(dòng)收集類種所有變量的賦值動(dòng)作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的疫剃,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在他之后的變量巢价,在前面的靜態(tài)語句塊可以賦值牲阁,但是不能訪問固阁,如下:
package com.designmodel.javap;
/**
* 測試
*
* @author 15620646321@163.com
* @date 2017年5月9日
*/
public class TestCLInit {
static {
i = 0;
//Cannot reference a field before it is defined
// System.out.println(i);
}
static int i = 1;
}
2)<clinit>()方法與類的構(gòu)造函數(shù)不同,他不需要顯示的調(diào)用父類構(gòu)造器城菊,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前备燃,父類的<clinit>()方法已經(jīng)執(zhí)行完畢,因此在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object凌唬。
3)由于父類的<clinit>()方法優(yōu)先執(zhí)行并齐,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作,如下
package com.designmodel.javap;
/**
* 測試
*
* @author 15620646321@163.com
* @date 2017年5月9日
*/
public class TestCLInit2 {
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.print(Sub.B);
}
}
輸出結(jié)果:
2
package com.designmodel.javap;
/**
* 測試
*
* @author 15620646321@163.com
* @date 2017年5月9日
*/
public class TestCLInit2 {
static class Parent {
static {
A = 2;
}
public static int A = 1;
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.print(Sub.B);
}
}
輸出結(jié)果:
1
從結(jié)果發(fā)現(xiàn)客税,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的况褪。
若有興趣,歡迎來加入群更耻,【Java初學(xué)者學(xué)習(xí)交流群】:458430385测垛,此群有Java開發(fā)人員、UI設(shè)計(jì)人員和前端工程師秧均。有問必答食侮,共同探討學(xué)習(xí),一起進(jìn)步熬北!
歡迎關(guān)注我的微信公眾號【Java碼農(nóng)社區(qū)】疙描,會(huì)定時(shí)推送各種干貨: