一、為什么需要類加載谒拴?類加載做些什么?
jvm執(zhí)行某段代碼的時(shí)候啸驯,需要把類相關(guān)信息加載到j(luò)vm的內(nèi)存中去客扎。
所謂類加載就是jvm把編譯后的class文件中的類信息加載到JVM內(nèi)存,并且生成對(duì)應(yīng)的class對(duì)象的過程
二罚斗、java類什么時(shí)候被加載徙鱼?
jvm并不是一開始就把所有的類都一次性加載到內(nèi)存中去,只有在需要的時(shí)候才會(huì)加載针姿,并且在jvm生命周期中一個(gè)類只會(huì)加載一次袱吆,類被加載過了,就不會(huì)再重復(fù)加載了距淫。所以什么時(shí)候才需要加載java類呢绞绒?
- 需要加載類的時(shí)機(jī)有以下幾種情況:
- 實(shí)例化類的時(shí)候,加載該類
- 實(shí)例化子類的時(shí)候榕暇,他未被初始化的父類被加載
- 調(diào)用類的靜態(tài)函數(shù)
- 調(diào)用類的靜態(tài)變量或賦值
- 反射使用class.forname()時(shí)蓬衡,ClassLoader.loadClass()時(shí)
Class.forName()和ClassLoader.loadClass()區(qū)別:
Class.forName():將類的.class文件加載到j(luò)vm中之外喻杈,還會(huì)對(duì)類進(jìn)行解釋,執(zhí)行類中的static塊狰晚;
ClassLoader.loadClass():只干一件事情筒饰,就是將.class文件加載到j(luò)vm中,不會(huì)執(zhí)行static中的內(nèi)容,只有在newInstance才會(huì)去執(zhí)行static塊 - java虛擬機(jī)啟動(dòng)的時(shí)候會(huì)加載啟動(dòng)類
- 類被加載的時(shí)候壁晒,他的靜態(tài)代碼塊瓷们、靜態(tài)方法及靜態(tài)變量會(huì)被加載,在他進(jìn)行到初始化這個(gè)步驟時(shí)秒咐,如果這個(gè)類的靜態(tài)代碼塊谬晕、靜態(tài)方法或靜態(tài)變量引用到了另一個(gè)類,則這個(gè)被引用的類也會(huì)被加載反镇。
三固蚤、類從哪里加載?
– 從本地系統(tǒng)中直接加載
– 通過網(wǎng)絡(luò)下載.class文件
– 從zip歹茶,jar等歸檔文件中加載.class文件
– 從專有數(shù)據(jù)庫(kù)中提取.class文件
– 將Java源文件動(dòng)態(tài)編譯為.class文件(動(dòng)態(tài)代理什么的)
四夕玩、類加載到哪里去?
編譯后的.class文件的二進(jìn)制數(shù)據(jù)加載到j(luò)vm的方法區(qū)內(nèi)惊豺,生成的Class對(duì)象放到j(luò)vm的堆燎孟,
Class對(duì)象封裝了類的數(shù)據(jù)結(jié)構(gòu),并且向程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口
五尸昧、類加載的時(shí)候做了什么揩页?類加載的流程?
類加載的過程包括了加載烹俗、驗(yàn)證爆侣、準(zhǔn)備、解析幢妄、初始化五個(gè)階段
他們的順序是加載兔仰、驗(yàn)證、準(zhǔn)備蕉鸳、解析乎赴、初始化 或者 加載、驗(yàn)證潮尝、準(zhǔn)備榕吼、初始化、解析
其中加載勉失、驗(yàn)證羹蚣、準(zhǔn)備的順序是固定的,解析和初始化的順序是不一定的戴质,這是為了支持java的運(yùn)行時(shí)綁定
需要注意的是度宦,這里的順序是指按某種順序開始進(jìn)行這一階段的任務(wù)踢匣,而不是串行的,不是開始并完成一項(xiàng)才進(jìn)行下一項(xiàng)的戈抄,這些階段都是交叉混合的進(jìn)行离唬。
- 加載
這里的加載是指把class字節(jié)碼文件用類加載器從各個(gè)來(lái)源裝載入內(nèi)存中
1、通過一個(gè)類的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流划鸽。
2输莺、將這個(gè)字節(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ì)象嫂用,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口。 - 驗(yàn)證
確保被加載的類的正確性丈冬,文件格式驗(yàn)證嘱函、語(yǔ)法語(yǔ)義驗(yàn)證等 - 準(zhǔn)備
類的靜態(tài)變量(static修飾的)在方法區(qū)中初始化零值(如0、0L埂蕊、null往弓、false等)
類的靜態(tài)常量(static final修飾的)在此時(shí)賦予代碼中設(shè)定的值
常量(final修飾)系統(tǒng)不會(huì)為其賦予默認(rèn)零值 - 解析
虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程
符號(hào)引用就是一組符號(hào)來(lái)描述目標(biāo),可以是任何字面量蓄氧。
直接引用就是直接指向目標(biāo)的指針函似、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄。 - 初始化
為類的靜態(tài)變量賦予正確的初始值
因?yàn)镴ava中對(duì)類變量進(jìn)行初始值設(shè)定有兩種方式:
①聲明類變量是指定初始值
②使用靜態(tài)代碼塊為類變量指定初始值
所以為類的靜態(tài)變量賦予正確的初始值就是執(zhí)行這兩項(xiàng)喉童,他們之間的優(yōu)先級(jí)是一樣的撇寞,如果同時(shí)包含多個(gè)靜態(tài)變量和靜態(tài)代碼塊,則按照自上而下的順序依次執(zhí)行堂氯。
如果開始初始化一個(gè)類的時(shí)候蔑担,發(fā)現(xiàn)他的父類還沒初始化,那就優(yōu)先初始化其父類咽白。(這時(shí)候判斷父類有沒進(jìn)行了加載钟沛、驗(yàn)證、準(zhǔn)備局扶、解析等,沒有的話就去先做這些然后再對(duì)父類初始化)
上面提到叁扫,靜態(tài)代碼塊和靜態(tài)變量引用的類也是一樣三妈,這個(gè)時(shí)候被引用的類沒有初始化就去做初始化類的操作(也需要想判斷有沒有加載、驗(yàn)證莫绣、準(zhǔn)備畴蒲、解析等)。
六对室、類用什么加載模燥?
用類加載器加載咖祭,類加載器包含以下幾種:
啟動(dòng)類加載器:Bootstrap ClassLoader,負(fù)責(zé)加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄蔫骂,下同)下么翰,或被-Xbootclasspath參數(shù)指定的路徑中的,并且能被虛擬機(jī)識(shí)別的類庫(kù)(如rt.jar辽旋,所有的java.*開頭的類均被Bootstrap ClassLoader加載)浩嫌。啟動(dòng)類加載器是無(wú)法被Java程序直接引用的。
擴(kuò)展類加載器:Extension ClassLoader补胚,該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)码耐,它負(fù)責(zé)加載DK\jre\lib\ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.*開頭的類)溶其,開發(fā)者可以直接使用擴(kuò)展類加載器骚腥。
應(yīng)用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn)瓶逃,它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類束铭,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器金闽,一般情況下這個(gè)就是程序中默認(rèn)的類加載器纯露。
自定義的類加載器
應(yīng)用程序都是由前三種類加載器互相配合進(jìn)行加載的,如果有必要代芜,我們還可以加入自定義的類加載器埠褪。因?yàn)镴VM自帶的ClassLoader只是懂得從本地文件系統(tǒng)加載標(biāo)準(zhǔn)的java class文件,因此如果編寫了自己的ClassLoader挤庇,便可以做到如下幾點(diǎn):
1)在執(zhí)行非置信代碼之前钞速,自動(dòng)驗(yàn)證數(shù)字簽名。
2)動(dòng)態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類嫡秕。
3)從特定的場(chǎng)所取得java class渴语,例如數(shù)據(jù)庫(kù)中和網(wǎng)絡(luò)中。
七昆咽、類加載器的加載機(jī)制驾凶?
? 全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí)掷酗,該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入调违,除非顯示使用另外一個(gè)類加載器來(lái)載入
? 父類委托,先讓父類加載器試圖加載該類泻轰,只有在父類加載器無(wú)法加載該類時(shí)才嘗試從自己的類路徑中加載該類
? 緩存機(jī)制技肩,緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí)浮声,類加載器先從緩存區(qū)尋找該Class虚婿,只有緩存區(qū)不存在旋奢,系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Class對(duì)象然痊,存入緩存區(qū)至朗。這就是為什么修改了Class后,必須重啟JVM玷过,程序的修改才會(huì)生效
雙親委派模型
雙親委派模型的工作流程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求爽丹,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委托給父加載器去完成辛蚊,依次向上粤蝎,因此,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中袋马,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí)初澎,即無(wú)法完成該加載,子加載器才會(huì)嘗試自己去加載該類虑凛。
雙親委派的例子:
1碑宴、當(dāng)AppClassLoader加載一個(gè)class時(shí),它首先不會(huì)自己去嘗試加載這個(gè)類桑谍,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成延柠。
2、當(dāng)ExtClassLoader加載一個(gè)class時(shí)锣披,它首先也不會(huì)自己去嘗試加載這個(gè)類贞间,而是把類加載請(qǐng)求委派給BootStrapClassLoader去完成。
3雹仿、如果BootStrapClassLoader加載失斣鋈取(例如在$JAVA_HOME/jre/lib里未查找到該class),會(huì)使用ExtClassLoader來(lái)嘗試加載胧辽;
4峻仇、若ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來(lái)加載邑商,如果AppClassLoader也加載失敗摄咆,則會(huì)報(bào)出異常ClassNotFoundException。
雙親委派模型意義:
- 系統(tǒng)類防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
- 保證Java程序安全穩(wěn)定運(yùn)行人断,不然可能會(huì)出現(xiàn)自己定義一個(gè)Object對(duì)象的情況出現(xiàn)
補(bǔ)充1:類的生命周期是什么豆同?
類的生命周期是加載、驗(yàn)證含鳞、準(zhǔn)備、解析芹务、初始化蝉绷、使用鸭廷、卸載
補(bǔ)充2:類對(duì)象實(shí)例化的初始化順序
實(shí)例化一個(gè)類的時(shí)候,初始化順序是:
父類靜態(tài)代碼塊熔吗、父類靜態(tài)變量(優(yōu)先級(jí)并列) --->
子類靜態(tài)代碼塊辆床、子類靜態(tài)變量 (優(yōu)先級(jí)并列)--->
父類代碼塊 --->
父類構(gòu)造函數(shù) --->
子類代碼塊 --->
子類構(gòu)造函數(shù)
其中父類靜態(tài)代碼塊、父類靜態(tài)變量>子類靜態(tài)代碼塊桅狠、子類靜態(tài)變量這個(gè)是類加載的時(shí)候做的事讼载,所以只在jvm生命周期內(nèi)加載類的時(shí)候做一次,后面實(shí)例化這個(gè)類的對(duì)象就執(zhí)行代碼塊和構(gòu)造函數(shù)的代碼就好了