轉(zhuǎn)自:http://blog.csdn.net/a707854407/article/details/40684255
類加載器是一個(gè)用來(lái)加載類文件的一個(gè)類.Java源代碼通過(guò)javac編譯器編譯成類文件.然后jvm來(lái)執(zhí)行類文件中的字節(jié)碼來(lái)執(zhí)行程序.
類加載文件負(fù)責(zé)加載文件系統(tǒng)斧拍、網(wǎng)絡(luò)或其他來(lái)源的類文件.
有以下三種默認(rèn)使用的類加載器:
- 1拍棕、Bootstrap類加載器(啟動(dòng)類加載器)-----JRE/lib/rt.jar
- 2、Extension類加載器(擴(kuò)展類加載器)-----JRE/lib/ext或者java.ext.dirs指向的目錄
- 3督禽、Application類加載器(應(yīng)用程序類加載器)------ClassPath環(huán)境變量.由-classpath或-cp選項(xiàng)來(lái)定義.或者是jar中的Manifest的classpath屬性定義.
類加載器的工作原理
類加載器的工作原理基于三個(gè)機(jī)制:委托队塘、可見(jiàn)性和單一性.
委托機(jī)制
當(dāng)一個(gè)類加載和初始化的時(shí)候.類僅在有需要加載的時(shí)候被加載.假設(shè)你有一個(gè)應(yīng)用需要的類叫做“Abc.class”.首先加載這個(gè)類的請(qǐng)求由Application類加載器委托給它的父加載器Extension類加載器.然后再委托Bootstrap類加載器.Bootstrap類加載器會(huì)先看看rt.jar中有沒(méi)有這個(gè)類.因?yàn)椴](méi)有這個(gè)類.所以這個(gè)請(qǐng)求又回到Extension加載器.它會(huì)查看jre/lib/ext目錄下有沒(méi)有這個(gè)類.如果有這個(gè)類.那么它將被加載.而Application類加載器不會(huì)加載到這個(gè)類.如果這個(gè)類沒(méi)有被Extension類加載器找到.那么由Application加載器從classpath中尋找.記住classpath定義的是類文件的加載目錄.而PATH定義的是可執(zhí)行程序.如javac穴张、java等的執(zhí)行路徑.可見(jiàn)性機(jī)制
根據(jù)可見(jiàn)性機(jī)制.子類加載器可以看到父加載器加載的類.而反之則不行.所以下面的例子中.當(dāng)Abc.class已經(jīng)被Application類加載器加載過(guò)了.然后如果想要使用Extension類加載器加載這個(gè)類.將會(huì)拋出java.lang.ClassNotFoundException異常.單一性機(jī)制
根據(jù)這個(gè)機(jī)制.父加載器加載過(guò)的類不能被子加載器加載第二次.雖然重寫(xiě)違反委托和單一性機(jī)制的類加載器是可能的.但這樣做并不可取.
例如:
public class TestClassLoader {
public static void main(String[] args) {
System.out.println("ClassLoader.getClass().getClassLoader():" + TestClassLoader.class.getClassLoader());
try {
Class.forName("test.TestClassLoader",true,TestClassLoader.class.getClassLoader().getParent());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
當(dāng)你自定義一個(gè)類加載器時(shí)你應(yīng)該遵守上面三條
類加載的時(shí)機(jī):類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始.到卸載出內(nèi)存為止.它的整個(gè)生命周期包括為:加載撬统、驗(yàn)證、準(zhǔn)備铝穷、解析钠怯、初始化、使用和卸載7個(gè)階段.其中驗(yàn)證曙聂、準(zhǔn)備晦炊、解析是三個(gè)部分統(tǒng)稱為連接.
發(fā)生順序:
其中類加載的過(guò)程中包括了加載、驗(yàn)證宁脊、準(zhǔn)備断国、解析、初始化五個(gè)階段.在這個(gè)五個(gè)階段中.加載榆苞、驗(yàn)證稳衬、準(zhǔn)備和初始化這四個(gè)階段發(fā)生的順序是確定的.而解析階段不一定.它在某些情況可以在初始化階段之后開(kāi)始.這是為了支持java語(yǔ)言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定).另外注意這里的幾個(gè)階段是按順序開(kāi)始.但不是按順序進(jìn)行或完成.因?yàn)檫@些階段通常都是互相交叉地混合進(jìn)行的.通常在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另一個(gè)階段.
靜態(tài)綁定:即前期綁定.在程序執(zhí)行前方法已經(jīng)被綁定.此時(shí)由編譯器或其他連接程序?qū)崿F(xiàn).針對(duì)java、簡(jiǎn)單的可以理解為程序編譯期的綁定.java當(dāng)中的方法只有final坐漏、static薄疚、private和構(gòu)造方法是前期綁定的.動(dòng)態(tài)綁定:即晚期綁定.也叫運(yùn)行時(shí)綁定.在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(jìn)行綁定.在java中.幾乎所有的方法都是后期綁定.
類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作弄砍,但它在Java程序中起到的作用卻遠(yuǎn)遠(yuǎn)不限于類的加載階段。對(duì)于任意一個(gè)類输涕,都需要由它的類加載器和這個(gè)類本身一同確定其在就Java虛擬機(jī)中的唯一性,也就是說(shuō)慨畸,即使兩個(gè)類來(lái)源于同一個(gè)Class文件莱坎,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等寸士。這里的“相等”包括了代表類的Class對(duì)象的equals()檐什、isAssignableFrom()、isInstance()等方法的返回結(jié)果弱卡,也包括了使用instanceof關(guān)鍵字對(duì)對(duì)象所屬關(guān)系的判定結(jié)果乃正。
1、加載
加載是“類加載”過(guò)程的一個(gè)階段.在加載階段.虛擬機(jī)需要完成以下3件事情.
1)婶博、通過(guò)一個(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)、在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象.作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口.2凡人、驗(yàn)證
驗(yàn)證是連接階段的第一步.這一階段的目的是為了確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求.并且不會(huì)危害虛擬機(jī)自身的安全.
驗(yàn)證階段是非常重要的.這個(gè)階段直接決定了java虛擬機(jī)是否能承受惡意代碼的攻擊.從執(zhí)行性能角度上講.驗(yàn)證階段的工作量在虛擬機(jī)的類加載子系統(tǒng)又占了相當(dāng)大的一部分.
如果驗(yàn)證到輸入的字節(jié)流不符合Class文件的約束.虛擬機(jī)就應(yīng)拋出一個(gè)java.lang.VerifyError異趁常或其子類異常.
驗(yàn)證階段大致上會(huì)完成下面4個(gè)階段的檢驗(yàn)動(dòng)作:
1)文件格式驗(yàn)證
2)元數(shù)據(jù)驗(yàn)證
3)字節(jié)碼驗(yàn)證
4)符號(hào)引用驗(yàn)證3、準(zhǔn)備
準(zhǔn)備階段是正式為類的變量分配內(nèi)存并設(shè)置類變量初始化的階段.這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配.這個(gè)階段中有兩個(gè)容易產(chǎn)生混淆的概念.
首先.這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量).而不包括實(shí)例變量.實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中.其次.這里所說(shuō)的“初始化”在通常情況下是數(shù)據(jù)類型的零值.
假設(shè)一個(gè)類變量的定義為: public static int value = 123;
那變量value在準(zhǔn)備階段過(guò)后的初始值為0而不是123.因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何java方法.而把value賦值為123的putstatic指令是程序被編譯后.存放于類構(gòu)造器<client>()方法之中.所以把value賦值為123的動(dòng)作在初始化階段才會(huì)執(zhí)行.或者.我們可以理解為static final常量在編譯期就將其結(jié)果放入了調(diào)用它的類的常量池中.4挠轴、解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程
解析動(dòng)作主要針對(duì)類或接口传睹、字段、類方法岸晦、接口方法欧啤、方法類型、方法句柄和調(diào)用點(diǎn)限定符7種符號(hào)引用進(jìn)行.分別對(duì)應(yīng)于常量池的CONSTANT_Class_info启上、CONSTANT_Fieldref_info邢隧、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info冈在、CONSTANT_MethodType_info府框、CONSTANT_MethodHandle_info和CONSTANT_InovkeDynamic_info 7種常量類型.后面三種是與JDK1.7動(dòng)態(tài)語(yǔ)言支持息息相關(guān)的.5、初始化
類初始化階段是類加載過(guò)程中的最后一步.前面的類加載過(guò)程中.除了在加載用戶應(yīng)用程序可以通過(guò)自定義類加載器去定義之外.其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制.到了初始化階段.才能真正地執(zhí)行類中定義的java程序代碼(或者說(shuō)是字節(jié)碼)
準(zhǔn)備階段.變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始值.而在初始化階段.則根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其他資源.或者可以從另外一個(gè)角度去表達(dá):初始化階段是執(zhí)行類構(gòu)造器<client>()方法的過(guò)程.另外.類構(gòu)造器<client>()方法和類實(shí)例化<init>()方法都是線程安全的.
例如:類構(gòu)造器<client>()方法的測(cè)試
package test;
public class DeadLoopClass {
static
{
try {
System.out.println("DeadLoopClass init");
Thread.sleep(10000);
System.out.println("DeadLoopClass over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(holyshit.value);
}
}
class holyshit
{
public static int value = 123;
static
{
try {
System.out.println("holyshit init");
Thread.sleep(10000);
System.out.println("holyshit over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同時(shí)也明白.一般未使用到的類不會(huì)調(diào)用類構(gòu)造器<client>()的方法(也就是"static{}").如果main方法調(diào)用到了那個(gè)類的變量或?qū)嵗四莻€(gè)類才會(huì)調(diào)用.
例如:實(shí)例化類<init>()方法的測(cè)試
public class fuck {
static
{
try {
Thread.sleep(10000);
System.out.println("執(zhí)行完");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
fuck f = new fuck();
fuck f2 = new fuck();
}
}