1.加載境蔼,將二進(jìn)制字節(jié)流加載到方法區(qū),然后在java堆中實例化一個java.lang.Class類的對象
2.驗證:文件, 驗證class文件格式規(guī)范-魔數(shù)、主次版本幻碱、文件編碼;元數(shù)據(jù)细溅,對字節(jié)碼描述的信息進(jìn)行語義分析:類父類褥傍、final、抽象實現(xiàn)喇聊,字節(jié)碼摔桦,進(jìn)行數(shù)據(jù)流和控制流分析:類的方法、類型轉(zhuǎn)換,符號引用-是否存在指定類邻耕、權(quán)限
3.準(zhǔn)備:類變量(static變量)賦0值(注:不是指定值)鸥咖,final變量直接賦指定值
4.解析:符號引用轉(zhuǎn)為直接引用,主要針對類或接口兄世、字段啼辣、類方法、接口方法四類符號引用進(jìn)行
5.初始化:遇到new御滩、getstatic鸥拧、putstatic或invokestatic這4條字節(jié)碼指令時,反射時如果類沒有進(jìn)行過初始化削解,則需先觸發(fā)其初始化富弦,包括對靜態(tài)變量賦指定值、靜態(tài)代碼塊的初始化氛驮,1.按代碼順序初始化腕柜,2.父類先于子類初始化,3.clinit ()方法不是必須的4.接口也有clint矫废,5.線程安全
驗證-準(zhǔn)備-解析稱為連接階段
init是instance實例構(gòu)造器盏缤,對非靜態(tài)變量解析初始化,而clinit是class類構(gòu)造器對靜態(tài)變量蓖扑,靜態(tài)代碼塊進(jìn)行初始化唉铜。
<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊static{}中的語句合并產(chǎn)生的
對于這五種會觸發(fā)類進(jìn)行初始化的場景,這五種場景中的行為稱為對一個類進(jìn)行 主動引用律杠。除此之外潭流,所有引用類的方式,都不會觸發(fā)初始化柜去,稱為 被動引用幻枉。
類的實例化是指創(chuàng)建一個類的實例(對象)的過程;
類的初始化是指為類中各個類成員(被static修飾的成員變量)賦初始值的過程诡蜓,是類生命周期中的一個階段熬甫。
通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類初始化:例子
實例初始化不一定要在類初始化結(jié)束之后才開始初始化蔓罚。例子
創(chuàng)建一個對象常常需要經(jīng)歷如下幾個過程:父類的類構(gòu)造器<clinit>() -> 子類的類構(gòu)造器<clinit>() -> 父類的成員變量和實例代碼塊 -> 父類的構(gòu)造函數(shù) -> 子類的成員變量和實例代碼塊 -> 子類的構(gòu)造函數(shù)椿肩。
淺克隆:復(fù)制的對象時不同的對象豺谈,對象內(nèi)部的基本數(shù)據(jù)克隆正常郑象,但是引用類型不會克隆,只會指向同一個
引用靜態(tài)常量時茬末,常量在編譯階段會存入類的常量池中厂榛,本質(zhì)上并沒有直接引用到定義常量的類文章最后有說明
類加載的過程(加載盖矫、驗證、準(zhǔn)備击奶、解析辈双、初始化)
JVM(三):類加載機(jī)制(類加載過程和類加載器)
深入理解Java類加載器
1.加載
“加載”是”類加載”過程的一個階段。由類加載器完成柜砾,在加載階段湃望,虛擬機(jī)需要完成以下3件事情:
1.通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。(并沒有指明要從一個Class文件中獲取痰驱,可以從其他渠道证芭,譬如:網(wǎng)絡(luò)、動態(tài)生成担映、數(shù)據(jù)庫等)废士;
2.將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)。
3.在內(nèi)存中生成一個代表這個類的java.lang.Class對象蝇完,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口官硝。
相對于類加載過程的其他階段,加載階段(準(zhǔn)備地說四敞,是加載階段中獲取類的二進(jìn)制字節(jié)流的動作)是開發(fā)期可控性最強(qiáng)的階段,因為加載階段可以使用系統(tǒng)提供的類加載器(ClassLoader)來完成拔妥,也可以由用戶自定義的類加載器完成忿危,開發(fā)人員可以通過定義自己的類加載器去控制字節(jié)流的獲取方式。
加載階段和連接階段(Linking)的部分內(nèi)容(如一部分字節(jié)碼文件格式驗證動作)是交叉進(jìn)行的没龙,加載階段尚未完成铺厨,連接階段可能已經(jīng)開始,
但這些夾在加載階段之中進(jìn)行的動作硬纤,仍然屬于連接階段的內(nèi)容解滓,這兩個階段的開始時間仍然保持著固定的先后順序。
2.驗證:
文件驗證:魔數(shù)開頭筝家、主次版本洼裤、文件編碼
元數(shù)據(jù)驗證:類相關(guān)驗證、父類溪王、抽象方法腮鞍、方法重載出錯
字節(jié)碼驗證:對類的方法體驗證、類型轉(zhuǎn)換
符號引用驗證:對類引用的常量池中的各種符號引用檢查能否找到莹菱、權(quán)限移国、public、private等
驗證是連接階段的第一步道伟,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求迹缀,并不會危害虛擬機(jī)的自身安全。
驗證階段是非常重要的,但不是必須的祝懂,它對程序運(yùn)行期沒有影響票摇。
1.文件格式驗證:
驗證class文件格式規(guī)范,
(1)是否以魔數(shù)0xCAFEBABE開頭嫂易。
(2)主兄朋、次版本號是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)。
(3)常量池的常量中是否有不被支持的常量類型(檢查常量tag標(biāo)志)怜械。
(4)指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量颅和。
(5)CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù)。
(6)Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息缕允。
......
2.元數(shù)據(jù)驗證:
這個階段是對字節(jié)碼描述的信息進(jìn)行語義分析峡扩,以保證起描述的信息符合java語言規(guī)范要求。
(1)這個類是否有父類(除了java.lang.Object之外障本,所有類都應(yīng)當(dāng)有父類)教届。
(2)這個類是否繼承了不允許被繼承的類(被final修飾的類)。
(3)如果這個類不是抽象類驾霜,是否實現(xiàn)了其父類或接口之中所要求實現(xiàn)的所有方法案训。
(4)類中的字段、方法是否與父類產(chǎn)生矛盾(例如覆蓋了父類的final字段粪糙,或者出現(xiàn)不符合規(guī)則的方法重載强霎,例如方法參數(shù)都一致,但返回值類型卻不同等等)蓉冈。
......
3.字節(jié)碼驗證:
進(jìn)行數(shù)據(jù)流和控制流分析城舞,這個階段對類的方法體進(jìn)行校驗分析,確定程序語義是合法的寞酿、符合邏輯的家夺。保證被校驗類的方法在運(yùn)行時不會產(chǎn)生危害虛擬機(jī)安全的事件,例如:
(1)保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作伐弹,例如不會出現(xiàn)類似這樣的情況:在操作數(shù)棧放置了一個int類型的數(shù)據(jù)拉馋,使用時卻按long類型來加載入本地變量表中。
(2)保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上惨好。
(3)保證方法體中的類型轉(zhuǎn)換是有效的椅邓,例如可以把一個子類對象賦值給父類數(shù)據(jù)類型,但是把父類對象賦值給子類數(shù)據(jù)類型昧狮,甚至把對象賦值給與它毫無繼承關(guān)系景馁、完全不相干的一個數(shù)據(jù)類型,則是危險不合法的逗鸣。
......
(Halting Problem:通過程序去校驗程序邏輯是無法做到絕對準(zhǔn)確的——不能通過程序準(zhǔn)確的檢查出程序是否能在有限時間之內(nèi)結(jié)束運(yùn)行合住。)
4.符號引用驗證:
符號引用中通過字符串描述的全限定名是否能(在常量池中)找到對應(yīng)的類绰精,符號引用類中的類,字段和方法的訪問性(private透葛、protected笨使、public、default)是否可被當(dāng)前類訪問僚害。
符號引用驗證可以看作是類對自身以外(常量池中的各種符號引用)的信息進(jìn)行匹配性校驗硫椰,通常需要校驗以下內(nèi)容:
(1)符號引用中通過字符串描述的全限定名是否能夠找到對應(yīng)的類。
(2)在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段萨蚕。
(3)符號引用中的類靶草、字段、方法的訪問性(private岳遥、protected奕翔、public、default)是否可被當(dāng)前類訪問浩蓉。
最后一個階段的校驗發(fā)生在虛擬機(jī)將符號引用轉(zhuǎn)化為直接引用的時候派继,這個轉(zhuǎn)化動作將在連接的第三個階段——解析階段中發(fā)生。符號引用驗證可以看做是對類自身以外的信息進(jìn)行匹配性的校驗捻艳,通常需要校驗以下內(nèi)容:
符號引用中通過字符串描述的全限定名是否能找到對應(yīng)的類驾窟。
在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段。
符號引用中的類认轨、字段和方法的訪問性(private绅络、protected、public好渠、default)是否可被當(dāng)前類訪問昨稼。
节视。拳锚。。寻行。霍掺。。
符號引用驗證的目的是確保解析動作能正常執(zhí)行拌蜘,如果無法通過符號引用驗證杆烁,將會拋出一個java.lang.IncompatibleClassChangeError異常的子類,如java.lang.IllegalAccessError简卧、java.lang.NoSuchFieldError兔魂、java.lang.NoSuchMethodError等。
驗證階段對于虛擬機(jī)的類加載機(jī)制來說举娩,是一個非常重要析校、但不一定是必要的階段构罗。如果所運(yùn)行的全部代碼都已經(jīng)被反復(fù)使用和驗證過,在實施階段就可以考慮使用-Xverify:none參數(shù)來關(guān)閉大部分的類驗證措施
3. 準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值(\零值)的階段智玻,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配遂唧。這時候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實例變量吊奢,實例變量將會在對象實例化的時候隨著對象一起分配在Java堆中盖彭。
如果類字段的字段屬性表中存在ConstantValue(不變)屬性,那在準(zhǔn)備階段變量value就會被初始化為ConstantValue屬性所指定的值页滚,假設(shè)類變量value定義為:
public static final int value = 123;
編譯時Javac將會為value生成ConstantValue屬性召边,在準(zhǔn)備階段虛擬機(jī)就會根據(jù)ConstantValue的設(shè)置將value賦值為123.
4.解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程。
- 符號引用(Symbolic References):
符號引用以一組符號來描述所引用的目標(biāo)逻谦,符號可以是任何形式的字面量掌实,只要使用時能無歧義地定位到目標(biāo)即可。符號引用與虛擬機(jī)實現(xiàn)的內(nèi)存布局無關(guān)邦马,引用的目標(biāo)對象并不一定已經(jīng)加載到內(nèi)存中贱鼻。
Java虛擬機(jī) 9 :Java 類加載機(jī)制
符號引用其實是屬于編譯原理方面的概念,符號引用包括了下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
這么說可能不太好理解滋将,結(jié)合實際看一下邻悬,寫一段很簡單的代碼:
package com.xrq.test6;
public class TestMain
{
private static int i;
private double d;
public static void print()
{
}
private boolean trueOrFalse()
{
return false;
}
}
用javap把這段代碼的.class反編譯一下:
Constant pool:
#1 = Class #2 // com/xrq/test6/TestMain
#2 = Utf8 com/xrq/test6/TestMain
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 i
#6 = Utf8 I
#7 = Utf8 d
#8 = Utf8 D
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Methodref #3.#13 // java/lang/Object."<init>":()V
#13 = NameAndType #9:#10 // "<init>":()V
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/xrq/test6/TestMain;
#18 = Utf8 print
#19 = Utf8 trueOrFalse
#20 = Utf8 ()Z
#21 = Utf8 SourceFile
#22 = Utf8 TestMain.java
看到Constant Pool
也就是常量池中有22項內(nèi)容,其中帶Utf8
的就是符號引用随闽。比如#2父丰,它的值是com/xrq/test6/TestMain
,表示的是這個類的全限定名掘宪;又比如#5為i蛾扇,#6為I,它們是一對的魏滚,表示變量時Integer(int)類型的镀首,名字叫做i;#6為D鼠次、#7為d也是一樣更哄,表示一個Double(double)類型的變量,名字為d腥寇;#18成翩、#19表示的都是方法的名字。
那其實總而言之赦役,符號引用和我們上面講的是一樣的麻敌,是對于類、變量掂摔、方法的描述术羔。符號引用和虛擬機(jī)的內(nèi)存布局是沒有關(guān)系的职辅,引用的目標(biāo)未必已經(jīng)加載到內(nèi)存中了。
- 直接引用(Direct References):
直接引用可以是直接指向目標(biāo)的指針聂示、相對偏移量或是一個能間接定位到目標(biāo)的句柄域携。如果有了直接引用,那么引用的目標(biāo)一定是已經(jīng)存在于內(nèi)存中鱼喉。
虛擬機(jī)規(guī)范并沒有規(guī)定解析階段發(fā)生的具體時間,所以虛擬機(jī)實現(xiàn)會根據(jù)需要來判斷秀鞭,到底是在類被加載器加載時就對常量池中的符號引用進(jìn)行解析,還是等到一個符號引用將要被使用前才去解析它扛禽。
解析的動作主要針對類或接口锋边、字段、類方法编曼、接口方法四類符號引用進(jìn)行豆巨。分別對應(yīng)編譯后常量池內(nèi)的CONSTANT_Class_Info、CONSTANT_Fieldref_Info掐场、CONSTANT_Methodef_Info往扔、CONSTANT_InterfaceMethoder_Info四種常量類型。
1.類熊户、接口的解析
2.字段解析
3.類方法解析
4.接口方法解析
1.類或接口的解析
假設(shè)當(dāng)前代碼所處的類為D萍膛,如果要把一個從未解析過的符號引用N解析為一個類或接口C的引用,那虛擬機(jī)完成整個解析過程需要以下3個步驟:
(1)如果C不是一個數(shù)組類型嚷堡,那虛擬機(jī)將會把代表N的全限定名傳遞給D的類加載器去加載這個類C蝗罗。
(2)如果C是一個數(shù)組類型,并且數(shù)組的元素類型為對象蝌戒,那將會按照第1點的規(guī)則加載數(shù)組元素類型串塑。
(3)如果上面的步驟沒有出現(xiàn)任何異常,那么C在虛擬機(jī)中實際上已經(jīng)成為了一個有效的類或接口了北苟,但在解析完成之前還要進(jìn)行符號引用驗證桩匪,確認(rèn)D是否具有對C的訪問權(quán)限。如果發(fā)現(xiàn)不具備訪問權(quán)限粹淋,則拋出java.lang.IllegalAccessError
異常吸祟。
2.字段解析
首先解析字段表內(nèi)class_index項中索引的CONSTANT_Class_info符號引用瑟慈,也就是字段所屬的類或接口的符號引用桃移,如果解析完成,將這個字段所屬的類或接口用C表示葛碧,虛擬機(jī)規(guī)范要求按照如下步驟對C進(jìn)行后續(xù)字段的搜索借杰。
(1)如果C 本身就包含了簡單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個字段的直接引用进泼,查找結(jié)束蔗衡。
(2)否則纤虽,如果C中實現(xiàn)了接口,將會按照繼承關(guān)系從下往上遞歸搜索各個接口和它的父接口如果接口中包含了簡單名稱和字段描述符都與目標(biāo)相匹配的字段绞惦,則返回這個字段的直接引用逼纸,查找結(jié)束。
(3)否則济蝉,如果C 不是java.lang.Object的話杰刽,將會按照繼承關(guān)系從下往上遞歸搜索其父類,如果在父類中包含了簡單名稱和字段描述符都與目標(biāo)相匹配的字段王滤,則返回這個字段的直接引用贺嫂,查找結(jié)束。
(4)否則雁乡,查找失敗第喳,拋出java.lang.NoSuchFieldError
異常。
如果查找過程成功返回了引用踱稍,將會對這個字段進(jìn)行權(quán)限驗證曲饱,如果發(fā)現(xiàn)不具備對字段的訪問權(quán)限,將拋出java.lang.IllegalAccessErro
r異常珠月。
如果有一個同名字段同時出現(xiàn)在C的接口和父類中渔工,或者同時在自己的父類或多個接口中出現(xiàn),那編譯器可能拒絕編譯桥温,并提示”The field xxx is ambiguous”引矩。
3.類方法解析
首先解析類方法表內(nèi)class_index項中索引的CONSTANT_Class_info符號引用,也就是方法所屬的類或接口的符號引用侵浸,如果解析完成旺韭,將這個類方法所屬的類或接口用C表示,虛擬機(jī)規(guī)范要求按照如下步驟對C進(jìn)行后續(xù)類方法的搜索掏觉。
(1)類方法和接口方法符號引用的常量類型定義是分開的区端,如果在類方法表中發(fā)現(xiàn)class_index中索引的C 是個接口惠况,那就直接拋出java.lang.IncompatibleClassChangeError異常举户。
(2)如果通過了第一步磺送,在類C 中查找是否有簡單名稱和描述符都與目標(biāo)相匹配的方法忧勿,如果有則返回這個方法的直接引用龙屉,查找結(jié)束啄育。
(3)否則罩润,在類C的父類中遞歸查找是否有簡單名稱和描述符都與目標(biāo)相匹配的方法赫舒,如果有則返回這個方法的直接引用羊娃,查找結(jié)束唐全。
(4)否則,在類C實現(xiàn)的接口列表以及他們的父接口中遞歸查找是否有簡單名稱和描述符都與目標(biāo)相匹配的方法蕊玷,如果存在相匹配的方法邮利,說明類C是一個抽象類這時查找結(jié)束弥雹,拋出java.lang.AbstractMethodError異常。
(5)否則延届,宣告方法查找失敗剪勿,拋出java.lang.NoSuchMethodError。
最后方庭,如果查找成功返回了直接引用窗宦,將會對這個方法進(jìn)行權(quán)限驗證,如果發(fā)現(xiàn)不具備此方法的訪問權(quán)限二鳄,則拋出java.lang.IllegalAccessError異常赴涵。
4.接口方法解析
首先解析接口方法表內(nèi)class_index項中索引的CONSTANT_Class_info符號引用,也就是方法所屬的類或接口的符號引用订讼,如果解析完成髓窜,將這個接口方法所屬的接口用C表示,虛擬機(jī)規(guī)范要求按照如下步驟對C進(jìn)行后續(xù)接口方法的搜索欺殿。
(1)與類解析方法不同寄纵,如果在接口方法表中發(fā)現(xiàn)class_index中的索引C是個類而不是個接口,那就直接拋出java.lang.IncompatibleClassChangeError異常脖苏。
(2)否則程拭,在接口C中查找是否有簡單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個方法的直接引用棍潘,查找結(jié)束恃鞋。
(3)否則,在接口C的父接口中遞歸查找亦歉,直到j(luò)ava.lang.Object類(查找范圍包括Object類)為止恤浪,看是否有簡單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個方法的直接引用肴楷,查找結(jié)束水由。
(4)否則,宣告方法查找失敗赛蔫,拋出java.lang.NoSuchMethodError砂客。
由于接口中所有的方法默認(rèn)都是public的,所以不存在訪問權(quán)限的問題呵恢,因此接口方法的符號解析應(yīng)當(dāng)不會拋出java.lang.IllegalAccessError異常鞠值。
5.初始化
類的初始化階段是類加載過程的最后一步,在準(zhǔn)備
階段瑰剃,類變量已賦過一次系統(tǒng)要求的初始值0值齿诉,而在初始化階段筝野,則是根據(jù)程序員通過程序制定的主觀計劃去初始化類變量和其他資源晌姚,或者可以從另外一個角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程
粤剧。
<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊static{}中的語句合并產(chǎn)生的
在以下四種情況下初始化過程會被觸發(fā)執(zhí)行:
- 遇到new、getstatic挥唠、putstatic或invokestatic這4條字節(jié)碼指令時抵恋,如果類沒有進(jìn)行過初始化,則需先觸發(fā)其初始化宝磨。生成這4條指令的最常見的java代碼場景是:1.使用new關(guān)鍵字實例化對象弧关;2-3.讀取或設(shè)置一個類的靜態(tài)變量的時候(final除外,被final修飾唤锉、已在編譯器把結(jié)果放入常量池)世囊;4.調(diào)用類的靜態(tài)方法的時候。
- 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候
- 當(dāng)初始化一個類的時候窿祥,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化株憾、則需要先出發(fā)其父類的初始化
- jvm啟動時,用戶指定一個執(zhí)行的主類(包含main方法的那個類)晒衩,虛擬機(jī)會先初始化這個類
類初始化階段是類加載過程的最后一步嗤瞎,到了這個階段才真正開始執(zhí)行類中定義的Java程序代碼(或者說是字節(jié)碼)。在準(zhǔn)備階段听系,變量已經(jīng)賦過一次系統(tǒng)要求的初始值0值贝奇,而在初始化階段,則對類變量賦指定值靠胜。
1.編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的掉瞳,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,而定義在它之后的變量浪漠,在前面的靜態(tài)語句塊可以賦值菠赚,但不能訪問,代碼解釋如下:
public class Test {
static {
i = 0; //給變量賦值可以正常編譯通過
System.out.print(i); //編譯器會提示“非法向前引用”
}
static int i = 1;
}
2.初始化方法執(zhí)行的順序郑藏,虛擬機(jī)會保證在子類的初始化方法執(zhí)行之前衡查,父類的初始化方法已經(jīng)執(zhí)行完畢,也就是說子類初始化時父類的所有靜態(tài)變量、靜態(tài)代碼塊 都執(zhí)行完了必盖。
3.clinit ()方法對于類或接口來說并不是必須的拌牲,如果一個類中沒有靜態(tài)語句塊,或者沒有對靜態(tài)變量的賦值操作歌粥,那么編譯器可以不為這個類生成clinit()方法塌忽。
4.接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的操作失驶,因此接口與類一樣都會生成clinit()方法土居,但與類不同的是,執(zhí)行接口的初始化方法之前,不需要先執(zhí)行父接口的初始化方法擦耀。只有當(dāng)父接口中定義的變量使用時棉圈,才會執(zhí)行父接口的初始化方法。另外眷蜓,接口的實現(xiàn)類在初始化時也一樣不會執(zhí)行接口的clinit()方法分瘾。
5.虛擬機(jī)會保證一個類的clinit()方法在多線程環(huán)境中被正確的加鎖、同步吁系,如果多個線程同時去初始化一個類德召,那么只會有一個線程去執(zhí)行這個類的clinit()方法,其他線程都需要阻塞等待汽纤,直到活動線程執(zhí)行類初始化方法完畢上岗。
. Java虛擬機(jī)加載.class過程
虛擬機(jī)把Class文件加載到內(nèi)存,然后進(jìn)行校驗蕴坪,解析和初始化液茎,最終形成java類型,這就是虛擬機(jī)的類加載機(jī)制辞嗡。加載捆等,驗證,準(zhǔn)備续室,初始化這5個階段的順序是確定的栋烤,
類的加載過程,必須按照這種順序開始挺狰。這些階段通常是相互交叉和混合進(jìn)行的明郭。解析階段在某些情況下,可以在初始化階段之后再開始---為了支持java語言的運(yùn)行時綁定丰泊。
Java虛擬機(jī)規(guī)范中薯定,沒有強(qiáng)制約束什么時候要開始加載,但是瞳购,卻嚴(yán)格規(guī)定了幾種情況必須進(jìn)行初始化(加載话侄,驗證,準(zhǔn)備則需要在初始化之前開始):
1)遇到 new学赛、getstatic年堆、putstatic、或者invokestatic 這4條字節(jié)碼指令盏浇,如果沒有類沒有進(jìn)行過初始化变丧,則觸發(fā)初始化
2)使用java.lang.reflect包的方法,對壘進(jìn)行反射調(diào)用的時候绢掰,如果沒有初始化痒蓬,則先觸發(fā)初始化
3)初始化一個類時候童擎,如果發(fā)現(xiàn)父類沒有初始化,則先觸發(fā)父類的初始化
- 加載攻晒,驗證顾复,解析
加載就是通過指定的類全限定名,獲取此類的二進(jìn)制字節(jié)流炎辨,然后將此二進(jìn)制字節(jié)流轉(zhuǎn)化為方法區(qū)的數(shù)據(jù)結(jié)構(gòu)捕透,在內(nèi)存中生成一個代表這個類的Class對象聪姿。驗證是為了確
保Class文件中的字節(jié)流符合虛擬機(jī)的要求碴萧,并且不會危害虛擬機(jī)的安全。加載和驗證階段比較容易理解末购,這里就不再過多的解釋破喻。解析階段比較特殊,解析階段是虛擬機(jī)
將常量池中的符號引用轉(zhuǎn)換為直接引用的過程盟榴。如果想明白解析的過程曹质,得先了解一點class文件的一些信息。class文件采用一種類似C語言的結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲我們編
碼的java類的各種信息擎场。其中羽德,class文件中常量池(constant_pool)是一個類似表格的倉庫,里面存儲了我們編寫的java類的類和接口的全限定名迅办,字段的名稱和描述符宅静,
方法的名稱和描述符。在java虛擬機(jī)將class文件加載到虛擬機(jī)內(nèi)存之后站欺,class類文件中的常量池信息以及其他的數(shù)據(jù)會被保存到j(luò)ava虛擬機(jī)內(nèi)存的方法區(qū)姨夹。我們知道class文件
的常量池存放的是java類的全名,接口的全名和字段名稱描述符矾策,方法的名稱和描述符等信息磷账,這些數(shù)據(jù)加載到j(luò)vm內(nèi)存的方法區(qū)之后,被稱做是符號引用贾虽。而把這些類的
全限定名逃糟,方法描述符等轉(zhuǎn)化為jvm可以直接獲取的jvm內(nèi)存地址,指針等的過程蓬豁,就是解析履磨。虛擬機(jī)實現(xiàn)可以對第一次的解析結(jié)果進(jìn)行緩存,避免解析動作的重復(fù)執(zhí)行庆尘。
在解析類的全限定名的時候剃诅,假設(shè)當(dāng)前所處的類為D,如果要把一個從未解析過的符號引用N解析為一個類或者接口C的直接引用驶忌,具體的執(zhí)行辦法就是虛擬機(jī)會把代表N的
全限定名傳遞給D的類加載器去加載這個類C矛辕。這塊可能不太好理解笑跛,但是我們可以直接理解為調(diào)用D類的ClassLoader來加載N,然后就完成了N--->C的解析聊品,就可以了飞蹂。