搜索的時(shí)候看了好幾篇文,自己就想記錄一遍分蓖,加深一下記憶唱凯,以下是原文的地址,受益匪淺睛低。
blog.csdn.net/ns_code/article/details/17881581(此篇推薦)
blog.csdn.net/boyupeng/article/details/47951037
hammer.coding.me/2016/10/26/jvm-1/
類的加載機(jī)制案狠,主要是為了把.class文件中的各種信息加載到內(nèi)存里,并且對(duì)這些數(shù)據(jù)進(jìn)行校驗(yàn)钱雷、轉(zhuǎn)換解析和初始化骂铁,最終為虛擬機(jī)可以使用的java類型。
類從被加載到卸載罩抗,是有一個(gè)生命周期的:
加載(Loading)拉庵、驗(yàn)證(Verification) 、準(zhǔn)備(Preparation)套蒂、解析(Resolution)钞支、初始化(Initialization)、使用(Using)和卸載(Unloading)七個(gè)階段操刀。
其中驗(yàn)證烁挟、準(zhǔn)備和解析三個(gè)部分被統(tǒng)稱為連接(Linking)。
由于Java可以進(jìn)行動(dòng)態(tài)擴(kuò)展骨坑,這就意味著可以進(jìn)行動(dòng)態(tài)加載和動(dòng)態(tài)鏈接信夫。這樣上圖中的加載、驗(yàn)證卡啰、準(zhǔn)備、初始化和卸載這五個(gè)步驟的順序是確定的警没。但是解析就不一定了匈辱,它可以等到初始化完了再開(kāi)始。所以上述的生命周期中每一個(gè)階段都是互相價(jià)差混合式進(jìn)行的杀迹,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另外一個(gè)階段亡脸。
這里我終于深刻理解到了‘解析等到初始化完了再開(kāi)始’是什么意思了#
結(jié)果:構(gòu)造塊 構(gòu)造塊 靜態(tài)塊 構(gòu)造塊
這里有一個(gè)綁定的概念:綁定指的是把一個(gè)方法的調(diào)用與方法所在的類關(guān)聯(lián)起來(lái),對(duì)Java來(lái)說(shuō)树酪,綁定分為靜態(tài)綁定和動(dòng)態(tài)綁定浅碾。
1)靜態(tài)綁定:編譯器綁定。在程序執(zhí)行前方法已經(jīng)被綁定了续语,Java當(dāng)中的方法只有final,static,private和構(gòu)造方法是前期綁定的垂谢。
2)動(dòng)態(tài)綁定:運(yùn)行時(shí)綁定。在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(jìn)行綁定疮茄。
我一直以為構(gòu)造方法不是運(yùn)行時(shí)才會(huì)綁定嗎滥朱?后來(lái)查了一下根暑,構(gòu)造方法鏈和動(dòng)態(tài)綁定是倆概念。
精確使用的方法是編譯器綁定徙邻,在編譯階段排嫌,最佳方法名依賴于參數(shù)的靜態(tài)和控制引用的靜態(tài)類型所適合的方法。在這個(gè)一點(diǎn)上缰犁,設(shè)置方法的名稱淳地,這一步叫靜態(tài)重載。決定方法是哪一個(gè)類的版本帅容,這通過(guò)由虛擬機(jī)推斷出這個(gè)對(duì)象的運(yùn)行時(shí)類型來(lái)完成颇象,一旦知道運(yùn)行時(shí)類型,虛擬機(jī)就喚起繼承機(jī)制丰嘉,尋找方法的最終版本夯到,叫動(dòng)態(tài)綁定。
參考鏈接:http://blog.csdn.net/lingzhm/article/details/44116091
http://blog.csdn.net/lzm1340458776/article/details/26280607
一饮亏、加載
1)通過(guò)類全名來(lái)獲取定義此類的二進(jìn)制字節(jié)流耍贾。
2)將字節(jié)流所代表的靜態(tài)存儲(chǔ)及結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3)在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象路幸,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口荐开。
相對(duì)于類加載過(guò)程的其他階段,加載階段是開(kāi)發(fā)期可控性最強(qiáng)的階段简肴,因?yàn)榧虞d階段可以使用系統(tǒng)提供的類加載器(Class Loader)來(lái)完成晃听,也可以由用戶自定義的類加載器完成,開(kāi)發(fā)人員可以通過(guò)定義自己的類加載器去控制字節(jié)流的獲取方式砰识。
加載階段完成后能扒,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式有虛擬機(jī)實(shí)現(xiàn)自行定義辫狼,虛擬機(jī)并未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)初斑。然后在Java堆中實(shí)例化一個(gè)java.lang.Class類的對(duì)象,這個(gè)對(duì)象作為程序訪問(wèn)方法區(qū)中的這些類型數(shù)據(jù)的外部接口膨处。
二见秤、驗(yàn)證
驗(yàn)證是鏈接階段的第一步,這一步主要的目的是確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求真椿,并且不會(huì)危害虛擬機(jī)自身安全鹃答。
驗(yàn)證階段主要包括:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證突硝、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證测摔。(具體意思就不寫啦)
三、準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段狞换,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配避咆。這個(gè)階段中有兩個(gè)容易產(chǎn)生混淆的知識(shí)點(diǎn)舟肉,首先是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static 修飾的變量),而不包括實(shí)例變量查库,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配到Java堆中路媚。其次是這里所說(shuō)的初始值通常情況下是數(shù)據(jù)類型的零值,假設(shè)
public static int value=12;
那么變量value在準(zhǔn)備階段過(guò)后的初始值為0而不是12樊销,因?yàn)檫@個(gè)時(shí)候尚未開(kāi)始執(zhí)行任何Java方法整慎,而把value賦值為12的指令是程序被編譯后,存放于類構(gòu)造器()方法之中围苫,所以把value賦值為12的動(dòng)作將在初始化階段才會(huì)被執(zhí)行裤园。
有一些特殊情況,如果類字段的字段屬性表中存在ConstantValue屬性剂府,那么在準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值拧揽,假如:
public static final int value =123;
編譯時(shí)javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)備將value設(shè)置為123腺占。
特別注意幾點(diǎn):
1)對(duì)基本數(shù)據(jù)類型來(lái)說(shuō)淤袜,對(duì)于類變量(static)和全局變量,如果不顯式地對(duì)其賦值而直接使用衰伯,則系統(tǒng)會(huì)為其賦予默認(rèn)的零值铡羡,而對(duì)于局部變量來(lái)說(shuō),在使用前必須顯式地為其賦值意鲸,否則編譯時(shí)不通過(guò)烦周。
2)對(duì)于同時(shí)被static和final修飾的常量,必須在聲明的時(shí)候就為其顯式地賦值怎顾,否則編譯時(shí)不通過(guò)读慎;而只被final修飾的常量則既可以在聲明時(shí)顯式地為其賦值,也可以在類初始化時(shí)顯式地為其賦值槐雾,總之贪壳,在使用前必須為其顯式地賦值,系統(tǒng)不會(huì)為其賦予默認(rèn)零值蚜退。
3)對(duì)于引用數(shù)據(jù)類型reference來(lái)說(shuō),如數(shù)組引用彪笼、對(duì)象引用等钻注,如果沒(méi)有對(duì)其進(jìn)行顯式地賦值而直接使用,系統(tǒng)都會(huì)為其賦予默認(rèn)的零值配猫,即null幅恋。
如果在數(shù)組初始化時(shí)沒(méi)有對(duì)數(shù)組中的各元素賦值,那么其中的元素將根據(jù)對(duì)應(yīng)的數(shù)據(jù)類型而被賦予默認(rèn)的零值泵肄。
ConstantValue屬性:
.class中的屬性捆交,通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值淑翼,只有被static修飾的變量才可以使用這項(xiàng)屬性。非static類型的變量的賦值是在實(shí)例構(gòu)造器方法中進(jìn)行的品追。在實(shí)際程序中玄括,只有同時(shí)被final和static修飾的字段才有ConstantValue屬性, 且限于基本類型和String肉瓦。編譯時(shí)javac將會(huì)為該常量生成ConstantValue屬性遭京,在類加載的準(zhǔn)備階段虛擬機(jī)便會(huì)根基ConstantValue為常量設(shè)置相應(yīng)的值,如果該變量沒(méi)有被final修飾泞莉,或者并非基本類型及字符串哪雕,則選擇在類構(gòu)造器中進(jìn)行初始化。
問(wèn)題:
a.為什么ConstantValue的屬性值只限于基本類型和String鲫趁?
因?yàn)閺某A砍刂兄荒芤玫交绢愋秃蚐tring類型的字面量斯嚎。
b.final、static挨厚、static final修飾的字段賦值的區(qū)別堡僻?
1)static修飾的字段在加載過(guò)程中準(zhǔn)備階段被初始化,但是這個(gè)階段只會(huì)賦值一個(gè)默認(rèn)的值(0或者null而并非定義變量設(shè)置的值)初始化階段在類構(gòu)造器中才會(huì)賦值為變量定義的值幽崩。
2)final修飾的字段在運(yùn)行時(shí)被初始化苦始,可以直接賦值,也可以在實(shí)例構(gòu)造器中賦值慌申,賦值后不可修改陌选。
3)tatic final修飾的字段在javac編譯時(shí)生成comstantValue屬性,在類加載的準(zhǔn)備階段直接把constantValue的值賦給該字段蹄溉。
四咨油、解析
解析階段是虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。
符號(hào)引用:符號(hào)引用是一組符號(hào)來(lái)描述所引用的目標(biāo)對(duì)象柒爵,符號(hào)可以是任何形式的字面量役电,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān)棉胀,引用的目標(biāo)對(duì)象并不一定已經(jīng)加載到內(nèi)存中法瑟。
直接引用:直接引用可以是直接指向目標(biāo)對(duì)象的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄唁奢。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān)的霎挟,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同,如果有了直接引用麻掸,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在酥夭。
1、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對(duì)數(shù)組類型,還是普通的對(duì)象類型的引用熬北,從而進(jìn)行不同的解析疙描。
2、字段解析:對(duì)字段進(jìn)行解析時(shí)讶隐,會(huì)先在本類中查找是否包含有簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段起胰,如果有,則查找結(jié)束整份;如果沒(méi)有待错,則會(huì)按照繼承關(guān)系從上往下遞歸搜索該類所實(shí)現(xiàn)的各個(gè)接口和它們的父接口,還沒(méi)有烈评,則按照繼承關(guān)系從上往下遞歸搜索其父類火俄,直至查找結(jié)束,查找流程如下圖所示:
class Super{
public static int m = 11;
static{
System.out.println("執(zhí)行了super類靜態(tài)語(yǔ)句塊");
}
}
class Father extends Super{
public static int m = 33;
static{
System.out.println("執(zhí)行了父類靜態(tài)語(yǔ)句塊");
}
}
class Child extends Father{
static{
System.out.println("執(zhí)行了子類靜態(tài)語(yǔ)句塊");
}
}
public class StaticTest{
public static void main(String[] args){
System.out.println(Child.m);
}
}
執(zhí)行結(jié)果:
執(zhí)行了super類靜態(tài)語(yǔ)句塊
執(zhí)行了父類靜態(tài)語(yǔ)句塊
33
satic變量在準(zhǔn)備的時(shí)候已經(jīng)擁有默認(rèn)值了讲冠,所以m在當(dāng)時(shí)都是0瓜客,當(dāng)Child.m這行代碼被執(zhí)行的時(shí)候,在Child類中沒(méi)有找到m這個(gè)字段竿开,那根據(jù)上述解析的定義谱仪,他就會(huì)去父類或者實(shí)現(xiàn)的接口上去找,于是他就找到了Fater類否彩,他找到了之后疯攒,就停止了。然后執(zhí)行的是father的方法列荔。而Child類根本不會(huì)被初始化敬尺。而身為father的父類super被初始化了。
如果注釋掉Father類中對(duì)m定義的那一行贴浙,則輸出結(jié)果如下:
執(zhí)行了super類靜態(tài)語(yǔ)句塊
11
這時(shí)候Child類中沒(méi)有找到m這個(gè)字段砂吞,父類里也沒(méi)有,所以child和Father都沒(méi)有被初始化崎溃。只有super被初始化了蜻直,并且m=11
最后需要注意:理論上是按照上述順序進(jìn)行搜索解析,但在實(shí)際應(yīng)用中袁串,虛擬機(jī)的編譯器實(shí)現(xiàn)可能要比上述規(guī)范要求的更嚴(yán)格一些概而。如果有一個(gè)同名字段同時(shí)出現(xiàn)在該類的接口和父類中,或同時(shí)在自己或父類的接口中出現(xiàn)囱修,編譯器可能會(huì)拒絕編譯到腥。如果對(duì)上面的代碼做些修改,將Super改為接口蔚袍,并將Child類繼承Father類且實(shí)現(xiàn)Super接口,那么在編譯時(shí)會(huì)報(bào)出如下錯(cuò)誤:
StaticTest.java:24: 對(duì) m 的引用不明確,F(xiàn)ather 中的 變量 m 和 Super 中的 變量 m
都匹配
System.out.println(Child.m);
^
1 錯(cuò)誤
3啤咽、類方法解析:對(duì)類方法的解析與對(duì)字段解析的搜索步驟差不多晋辆,只是多了判斷該方法所處的是類還是接口的步驟,而且對(duì)類方法的匹配搜索宇整,是先搜索父類瓶佳,再搜索接口。
4鳞青、接口方法解析:與類方法解析步驟類似霸饲,知識(shí)接口不會(huì)有父類,因此臂拓,只遞歸向上搜索父接口就行了厚脉。
五、初始化
類的初始化階段是類加載過(guò)程的最后一步胶惰,在準(zhǔn)備階段傻工,類變量已賦過(guò)一次系統(tǒng)要求的初始值,而在初始化階段孵滞,則是根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其他資源中捆,或者可以從另外一個(gè)角度來(lái)表達(dá):初始化階段是執(zhí)行類構(gòu)造器()方法的過(guò)程。在以下四種情況下初始化過(guò)程會(huì)被觸發(fā)執(zhí)行:
1.遇到new坊饶、getstatic泄伪、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有進(jìn)行過(guò)初始化匿级,則需先觸發(fā)其初始化蟋滴。生成這4條指令的最常見(jiàn)的java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾根蟹、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候脓杉,以及調(diào)用類的靜態(tài)方法的時(shí)候。
2.使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候
3.當(dāng)初始化一個(gè)類的時(shí)候简逮,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化球散、則需要先出發(fā)其父類的初始化
4.jvm啟動(dòng)時(shí),用戶指定一個(gè)執(zhí)行的主類(包含main方法的那個(gè)類)散庶,虛擬機(jī)會(huì)先初始化這個(gè)類
在上面準(zhǔn)備階段 public static int value = 12; 在準(zhǔn)備階段完成后 value的值為0蕉堰,而在初始化階調(diào)用了類構(gòu)造器<clinit>()方法,這個(gè)階段完成后value的值為12悲龟。
類構(gòu)造器<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生的屋讶,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量须教,定義在它之后的變量皿渗,在前面的靜態(tài)語(yǔ)句快可以賦值斩芭,但是不能訪問(wèn)。
類構(gòu)造器<clinit>()方法與類的構(gòu)造函數(shù)(實(shí)例構(gòu)造函數(shù)()方法)不同乐疆,它不需要顯式調(diào)用父類構(gòu)造划乖,虛擬機(jī)會(huì)保證在子類<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢挤土。因此在虛擬機(jī)中的第一個(gè)執(zhí)行的<clinit>()方法的類肯定是java.lang.Object琴庵。
由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句快要優(yōu)先于子類的變量賦值操作仰美。
<clinit>()方法對(duì)于類或接口來(lái)說(shuō)并不是必須的迷殿,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句,也沒(méi)有變量賦值的操作咖杂,那么編譯器可以不為這個(gè)類生成<clinit>()方法庆寺。
接口中不能使用靜態(tài)語(yǔ)句塊,但接口與類不太能夠的是翰苫,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的()方法止邮。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化奏窑。另外导披,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法。
虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確加鎖和同步埃唯,如果多個(gè)線程同時(shí)去初始化一個(gè)類撩匕,那么只會(huì)有一個(gè)線程執(zhí)行這個(gè)類的<clinit>()方法,其他線程都需要阻塞等待墨叛,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢止毕。如果一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,那就可能造成多個(gè)進(jìn)程阻塞漠趁。
class Father{
public static int a = 1;
static{
a = 2;
}
}
class Child extends Father{
public static int b = a;
}
public class ClinitTest{
public static void main(String[] args){
System.out.println(Child.b);
}
}
這個(gè)其實(shí)和上邊的例子本質(zhì)上是一樣的扁凛。輸出b為2。一下為轉(zhuǎn)載文中作者的解釋闯传,挺詳細(xì)的:
執(zhí)行上面的代碼谨朝,會(huì)打印出2,也就是說(shuō)b的值被賦為了2甥绿。
我們來(lái)看得到該結(jié)果的步驟字币。首先在準(zhǔn)備階段為類變量分配內(nèi)存并設(shè)置類變量初始值,這樣A和B均被賦值為默認(rèn)值0共缕,而后再在調(diào)用<clinit>()方法時(shí)給他們賦予程序中指定的值洗出。當(dāng)我們調(diào)用Child.b時(shí),觸發(fā)Child的<clinit>()方法图谷,根據(jù)規(guī)則2翩活,在此之前阱洪,要先執(zhí)行完其父類Father的<clinit>()方法,又根據(jù)規(guī)則1菠镇,在執(zhí)行<clinit>()方法時(shí)澄峰,需要按static語(yǔ)句或static變量賦值操作等在代碼中出現(xiàn)的順序來(lái)執(zhí)行相關(guān)的static語(yǔ)句,因此當(dāng)觸發(fā)執(zhí)行Father的<clinit>()方法時(shí)辟犀,會(huì)先將a賦值為1,再執(zhí)行static語(yǔ)句塊中語(yǔ)句绸硕,將a賦值為2堂竟,而后再執(zhí)行Child類的<clinit>()方法,這樣便會(huì)將b的賦值為2.
如果我們顛倒一下Father類中“public static int a = 1;”語(yǔ)句和“static語(yǔ)句塊”的順序玻佩,程序執(zhí)行后出嘹,則會(huì)打印出1。很明顯是根據(jù)規(guī)則1咬崔,執(zhí)行Father的<clinit>()方法時(shí)税稼,根據(jù)順序先執(zhí)行了static語(yǔ)句塊中的內(nèi)容,后執(zhí)行了“public static int a = 1;”語(yǔ)句垮斯。
另外郎仆,在顛倒二者的順序之后,如果在static語(yǔ)句塊中對(duì)a進(jìn)行訪問(wèn)(比如將a賦給某個(gè)變量)兜蠕,在編譯時(shí)將會(huì)報(bào)錯(cuò)扰肌,因?yàn)楦鶕?jù)規(guī)則1,它只能對(duì)a進(jìn)行賦值熊杨,而不能訪問(wèn)曙旭。
類的加載器
1、ClassLoader
所有自定義的類加載器都要繼承該類晶府。調(diào)用ClassLoaderl類的loadClass方法加載一個(gè)類桂躏,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化川陆。
2剂习、父委托機(jī)制(Parent Delegation)
1)類的加載過(guò)程采用父親委托機(jī)制,各個(gè)加載器按照父子關(guān)系形成了樹(shù)形結(jié)構(gòu)书劝,除了Java虛擬機(jī)自帶的根類加載器以外进倍,其余的類加載器都有切只有一個(gè)父加載器。如果當(dāng)前這個(gè)加載器想要加載一個(gè)Class购对,它不是直接自己就加載猾昆,他先去尋找他自己上邊的父加載器可不可以加載,如果不可以才進(jìn)行加載骡苞。
2)當(dāng)他找到某個(gè)類加載器能夠加載這個(gè)類的時(shí)候垂蜗,這個(gè)類加載器就被稱作為定義類加載器楷扬,而在他之下的所有子類加載器包括他自己本身,都被稱為初始類加載器贴见。
注意:加載器之間的父子關(guān)系實(shí)際上指的是加載器對(duì)象之間的包裝關(guān)系烘苹,而不是類之間的繼承關(guān)系。一對(duì)父子加載器可能是同一個(gè)加載器類的兩個(gè)實(shí)例片部,也可能不是镣衡。在子加載器對(duì)象中包裝了一個(gè)父加載器對(duì)象。
就是說(shuō)可能档悠,某兩個(gè)加載器loader1和loader2都是TestClassLoader類的實(shí)例廊鸥,并且loader2包裝了loader1,這樣來(lái)說(shuō)loader1就是loader2的父加載器辖所。
3)當(dāng)生成一個(gè)自定義的類加載器實(shí)例時(shí)惰说,如果沒(méi)有指定它的父加載器,那么系統(tǒng)類加載器就將成為該類加載器的父加載器缘回。
4) 父親委托機(jī)制的優(yōu)點(diǎn)是能夠提高軟件系統(tǒng)的安全性吆视。因?yàn)樵谶@個(gè)機(jī)制下,用戶自定義的類加載器就不能加載應(yīng)該由父加載器加載的可靠類酥宴,這樣就可以防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼啦吧。(就是說(shuō),我不能自己造一個(gè)比方說(shuō)含有惡意代碼的Java.lang.Object類出來(lái)加載)
5)命名空間
每個(gè)類加載器有自己的命名空間(加載器+所有父加載器加載的類)幅虑。在同一個(gè)命名空間中丰滑,不會(huì)出現(xiàn)類的完整名字相同的兩個(gè)類,在不同的命名空間中倒庵,有可能會(huì)出現(xiàn)類的完整名字相同的兩個(gè)類褒墨。
個(gè)人理解:一個(gè)類是可以加載兩次的,但是前提是由兩個(gè)不同的類加載器加載的擎宝,并且他倆之間沒(méi)有父子關(guān)系郁妈,不然在他們共有的命名空間里就會(huì)存在這個(gè)已經(jīng)加載的類。所以在每次進(jìn)行類加載之前绍申,加載器都會(huì)去命名空間里查看這個(gè)類是不是已經(jīng)被加載過(guò)了噩咪。
6)運(yùn)行時(shí)包
當(dāng)同一個(gè)類加載器加載的屬于相同包的類組成了運(yùn)行時(shí)。決定兩個(gè)類是不是屬于同一個(gè)運(yùn)行時(shí)包极阅,不僅要看他們的報(bào)名是否相同胃碾,還要看定義類加載器是否相同。只有歸屬于同一個(gè)運(yùn)行時(shí)包的類才能互相訪問(wèn)包可見(jiàn)的類和類成員筋搏。這樣才能避免用戶自定義的類來(lái)冒充核心類庫(kù)中的類仆百,去訪問(wèn)核心類庫(kù)的包課件成員。
類的卸載
1)當(dāng)一個(gè)類被加載奔脐、連接和初始化后俄周,它的生命周期就開(kāi)始了吁讨。當(dāng)代表這個(gè)類的Class對(duì)象不再被引用,即不可觸及時(shí)峦朗,Class對(duì)象就會(huì)結(jié)束生命周期建丧,那么這個(gè)類在方法區(qū)內(nèi)的數(shù)據(jù)也會(huì)被卸載,從而結(jié)束生命周期波势。就是說(shuō)一個(gè)類什么時(shí)候結(jié)束它的生命周期翎朱,實(shí)際上取決于代表它的Class對(duì)象什么時(shí)候結(jié)束生命周期。
2)由java虛擬機(jī)自帶的類(根加載器尺铣、擴(kuò)展類加載器闭翩、系統(tǒng)加載器)所加載的類,在虛擬機(jī)的生命周期中迄埃,始終不會(huì)卸載。虛擬機(jī)本身會(huì)始終引用這些加載器兑障,而這些類加載器則會(huì)始終引用他們所加載的類Class對(duì)象侄非。但是自定義的類是可以被卸載的。
3)在類加載器的內(nèi)部實(shí)現(xiàn)中流译,用一個(gè)Java集合來(lái)存放所加載的類的引用逞怨。另外,一個(gè)class對(duì)象總是會(huì)引用它的類加載器福澡。一個(gè)類的實(shí)例總是引用代表這個(gè)類的class對(duì)象叠赦。
個(gè)人理解:就是其實(shí)類的加載器和類的實(shí)例之間是雙向關(guān)聯(lián)的關(guān)系。class對(duì)象通過(guò)getClassLoader()來(lái)得到類加載器革砸,加載器通過(guò)getClass()方法來(lái)得到其加載的類除秀。
實(shí)例代碼:
這三行代碼的加載圖:
此時(shí)如果我一旦把所有的引用變量都置為Null,這樣Sample對(duì)象結(jié)束生命周期,MyClassLoader對(duì)象結(jié)束生命周期算利,代表Sample類的Class對(duì)象也會(huì)結(jié)束生命周期册踩,而該類在方法去內(nèi)的二進(jìn)制數(shù)據(jù)被卸載。(注意此處該對(duì)象在方法區(qū)的二進(jìn)制數(shù)據(jù)被卸載)
這樣當(dāng)再次加載該類的時(shí)候clazz變量引用的Class對(duì)象的哈希碼將會(huì)得到不同的數(shù)值效拭,因?yàn)樵谶@兩側(cè)中引用了不同的Class對(duì)象暂吉,在Java虛擬機(jī)的生命周期中,對(duì)該類進(jìn)行了先后兩次的加載缎患。