JVM相關(guān)(6)-- 類(lèi)加載機(jī)制

相關(guān)好文:面試官對(duì)于JVM類(lèi)加載機(jī)制的猛烈炮火掰担,你能頂住嗎悍抑?

https://juejin.im/post/5d1d4f8ef265da1b60291fec

類(lèi)是在運(yùn)行期間第一次使用時(shí)動(dòng)態(tài)加載的希坚,而不是一次性加載。因?yàn)槿绻淮涡约虞d崇堰,那么會(huì)占用很多的內(nèi)存虚青。

類(lèi)加載的機(jī)制的層次結(jié)構(gòu)

每個(gè)編寫(xiě)的”.java”拓展名類(lèi)文件都存儲(chǔ)著需要執(zhí)行的程序邏輯,這些”.java”文件經(jīng)過(guò)Java編譯器編譯成拓展名為”.class”的文件苞也,”.class”文件中保存著Java代碼經(jīng)轉(zhuǎn)換后的虛擬機(jī)指令洛勉,當(dāng)需要使用某個(gè)類(lèi)時(shí),虛擬機(jī)將會(huì)加載它的”.class”文件如迟,并創(chuàng)建對(duì)應(yīng)的class對(duì)象收毫,將class文件加載到虛擬機(jī)的內(nèi)存攻走,這個(gè)過(guò)程稱(chēng)為類(lèi)加載,這里我們需要了解一下類(lèi)加載的過(guò)程此再,如下:

加載:類(lèi)加載過(guò)程的一個(gè)階段昔搂,通過(guò)一個(gè)類(lèi)的完全限定查找此類(lèi)字節(jié)碼文件,并利用字節(jié)碼文件創(chuàng)建一個(gè)Class對(duì)象

1引润、通過(guò)一個(gè)類(lèi)的全限定名獲取描述此類(lèi)的二進(jìn)制字節(jié)流巩趁;

2痒玩、將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)保存為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)淳附;

3、在java堆中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象蠢古,作為訪(fǎng)問(wèn)方法區(qū)的入口奴曙;

驗(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)備:為類(lèi)變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類(lèi)變量的初始值嘱丢,使用的是方法區(qū)的內(nèi)存薪介。

實(shí)例變量不會(huì)在這階段分配內(nèi)存,它將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中越驻。

注意汁政,實(shí)例化不是類(lèi)加載的一個(gè)過(guò)程,類(lèi)加載發(fā)生在所有實(shí)例化操作之前缀旁,并且類(lèi)加載只進(jìn)行一次记劈,實(shí)例化可以進(jìn)行多次。

初始值一般為 0 值并巍,例如下面的類(lèi)變量 value 被初始化為 0 而不是 123目木。

public static int value = 123;

如果類(lèi)變量是常量,那么會(huì)按照表達(dá)式來(lái)進(jìn)行初始化懊渡,而不是賦值為 0刽射。

public static final int value = 123;

解析:主要將常量池中的【符號(hào)引用】替換為【直接引用】的過(guò)程。符號(hào)引用就是一組符號(hào)來(lái)描述目標(biāo)距贷,可以是任何字面量柄冲,而直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄忠蝗。其中解析過(guò)程在某些情況下可以在初始化階段之后再開(kāi)始现横,這是為了支持 Java 的動(dòng)態(tài)綁定。

解析的時(shí)候class已經(jīng)被加載到方法區(qū)的內(nèi)存中,因此要把符號(hào)引用轉(zhuǎn)化為直接引用戒祠,也就是能直接找到該類(lèi)實(shí)際內(nèi)存地址的引用骇两。

分為類(lèi)或接口的解析,字段解析姜盈,類(lèi)方法解析低千,接口方法解析。

初始化:類(lèi)加載最后階段馏颂,若該類(lèi)具有超類(lèi)示血,則對(duì)其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量(如前面只初始化了默認(rèn)值的static變量將會(huì)在這個(gè)階段賦值救拉,成員變量也將被初始化)难审。

初始化階段是執(zhí)行類(lèi)構(gòu)造器<clinit>方法的過(guò)程,<clinit>方法由類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊按照在源文件出現(xiàn)的順序合并而成亿絮,該合并操作由編譯器完成告喊。

1)<clinit>方法對(duì)于類(lèi)或接口不是必須的,如果一個(gè)類(lèi)中沒(méi)有靜態(tài)代碼塊派昧,也沒(méi)有靜態(tài)變量的賦值操作黔姜,那么編譯器不會(huì)生成<clinit>;

2)<clinit>方法與實(shí)例構(gòu)造器不同蒂萎,不需要顯式的調(diào)用父類(lèi)的<clinit>方法秆吵,虛擬機(jī)會(huì)保證父類(lèi)的<clinit>優(yōu)先執(zhí)行;

3)為了防止多次執(zhí)行<clinit>岖是,虛擬機(jī)會(huì)確保<clinit>方法在多線(xiàn)程環(huán)境下被正確的加鎖同步執(zhí)行帮毁,如果有多個(gè)線(xiàn)程同時(shí)初始化一個(gè)類(lèi),那么只有一個(gè)線(xiàn)程能夠執(zhí)行<clinit>方法豺撑,其它線(xiàn)程進(jìn)行阻塞等待烈疚,直到<clinit>執(zhí)行完成。

4)注意:執(zhí)行接口的<clinit>方法不需要先執(zhí)行父接口的<clinit>聪轿,只有使用父接口中定義的變量時(shí)爷肝,才會(huì)執(zhí)行。

對(duì)象的初始化順序:

????????????????? 基類(lèi)的static域加載

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

? ? ? ? ? ? ? ? ?子類(lèi)的static域加載?????????????????? ?????

??????????????????????? ????? |

??????????????????????? ????? |

基類(lèi)的成員變量初始化(基本類(lèi)型初始化為默認(rèn)值陆错,對(duì)象引用設(shè)為null灯抛,若有初值則初始化為初值)???????????

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

基類(lèi)的構(gòu)造器加載(若在構(gòu)造器中調(diào)用的方法在子類(lèi)中被覆蓋過(guò),則調(diào)用覆蓋后的方法)

??????????????????????? ????? |

??????????????????????? ????? |

???????????? 子類(lèi)的成員變量初始化???????????????????????????? ?????

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

? ? ? ? ? ? ? ? ?子類(lèi)的構(gòu)造器加載


類(lèi)初始化場(chǎng)景:

1音瓷,主動(dòng)引用

虛擬機(jī)中嚴(yán)格規(guī)定了有且只有5種情況必須對(duì)類(lèi)進(jìn)行初始化对嚼。

* 執(zhí)行new、getstatic绳慎、putstatic和invokestatic指令纵竖;

* 使用reflect對(duì)類(lèi)進(jìn)行反射調(diào)用漠烧;

* 初始化一個(gè)類(lèi)的時(shí)候,父類(lèi)還沒(méi)有初始化靡砌,會(huì)事先初始化父類(lèi)已脓;

* 啟動(dòng)虛擬機(jī)時(shí),需要初始化包含main方法的類(lèi)通殃;

* 在JDK1.7中度液,如果java.lang.invoke.MethodHandler實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic画舌、REF_invokeStatic的方法句柄堕担,并且這個(gè)方法句柄對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行初始化;

2骗炉,被動(dòng)引用

以上 5 種場(chǎng)景中的行為稱(chēng)為對(duì)一個(gè)類(lèi)進(jìn)行主動(dòng)引用照宝。除此之外,所有引用類(lèi)的方式都不會(huì)觸發(fā)初始化句葵,稱(chēng)為被動(dòng)引用。被動(dòng)引用的常見(jiàn)例子包括:

以下幾種情況兢仰,不會(huì)觸發(fā)類(lèi)初始化

1)通過(guò)子類(lèi)引用父類(lèi)的靜態(tài)字段乍丈,只會(huì)觸發(fā)父類(lèi)的初始化,而不會(huì)觸發(fā)子類(lèi)的初始化把将。

class Parent {

??? static int a = 100;

??? static {

???????System.out.println("parent init轻专!");

? ??}

}

class Child extends Parent {

??? static {

???????System.out.println("child init!");

??? }

}

public class Init{?

??? public static voidmain(String[] args){?

???????System.out.println(Child.a);?

??? }?

}?

輸出結(jié)果為:

parent init察蹲!

100

2)定義對(duì)象數(shù)組请垛,即通過(guò)數(shù)組定義來(lái)引用類(lèi),不會(huì)觸發(fā)該類(lèi)的初始化洽议。

public class Init{?

??? public static voidmain(String[] args){?

??????? Parent[] parents =new Parent[10];

??? }?

}?

無(wú)輸出宗收,說(shuō)明沒(méi)有觸發(fā)類(lèi)Parent的初始化,但是這段代碼做了什么亚兄?先看看生成的字節(jié)碼指令

anewarray指令為新數(shù)組分配空間混稽,并觸發(fā)[Lcom.ctrip.ttd.whywhy.Parent類(lèi)的初始化,這個(gè)類(lèi)由虛擬機(jī)自動(dòng)生成审胚。

3)常量在編譯期間會(huì)存入調(diào)用類(lèi)的常量池中匈勋,本質(zhì)上并沒(méi)有直接引用定義常量的類(lèi),不會(huì)觸發(fā)定義常量所在的類(lèi)膳叨。

class Const {

??? static final int A =100;

??? static {

???????System.out.println("Const init");

??? }

}

public class Init{?

??? public static voidmain(String[] args){?

??????? System.out.println(Const.A);?

??? }?

}?

輸出:

100

說(shuō)明沒(méi)有觸發(fā)類(lèi)Const的初始化洽洁,在編譯階段,Const類(lèi)中常量A的值100存儲(chǔ)到Init類(lèi)的常量池中菲嘴,這兩個(gè)類(lèi)在編譯成class文件之后就沒(méi)有聯(lián)系了饿自。

4)通過(guò)類(lèi)名獲取Class對(duì)象碎浇,不會(huì)觸發(fā)類(lèi)的初始化。

public class test {

?? public static voidmain(String[] args) throws ClassNotFoundException {

??????? Class c_dog =Dog.class;

??????? Class clazz =Class.forName("zzzzzz.Cat");

??? }

}

class Cat {

??? private String name;

??? private int age;

??? static {

???????System.out.println("Cat is load");

??? }

}

class Dog {

??? private String name;

?? ?private int age;

??? static {

???????System.out.println("Dog is load");

??? }

}

執(zhí)行結(jié)果:Cat is load璃俗,所以通過(guò)Dog.class并不會(huì)觸發(fā)Dog類(lèi)的初始化動(dòng)作奴璃。

5)通過(guò)Class.forName加載指定類(lèi)時(shí),如果指定參數(shù)initialize為false時(shí)城豁,也不會(huì)觸發(fā)類(lèi)初始化苟穆,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類(lèi)進(jìn)行初始化唱星。

public class test {

?? public static voidmain(String[] args) throws ClassNotFoundException {

??????? Class clazz =Class.forName("zzzzzz.Cat", false, Cat.class.getClassLoader());

??? }

}

class Cat {

??? private String name;

??? private int age;

??? static {

??????? System.out.println("Catis load");

??? }

}

6)通過(guò)ClassLoader默認(rèn)的loadClass方法雳旅,也不會(huì)觸發(fā)初始化動(dòng)作

new ClassLoader(){}.loadClass("zzzzzz.Cat");


JVM類(lèi)加載器

這便是類(lèi)加載的5個(gè)過(guò)程,而類(lèi)加載器的任務(wù)是根據(jù)一個(gè)類(lèi)的全限定名來(lái)讀取此類(lèi)的二進(jìn)制字節(jié)流到JVM中间聊,然后轉(zhuǎn)換為一個(gè)與目標(biāo)類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例攒盈,在虛擬機(jī)提供了3種類(lèi)加載器,啟動(dòng)(Bootstrap)類(lèi)加載器哎榴、擴(kuò)展(Extension)類(lèi)加載器型豁、系統(tǒng)(System)類(lèi)加載器(也稱(chēng)應(yīng)用類(lèi)加載器),下面分別介紹:

啟動(dòng)(Bootstrap)類(lèi)加載器

啟動(dòng)類(lèi)加載器主要加載的是JVM自身需要的類(lèi)尚蝌,這個(gè)類(lèi)加載使用C++語(yǔ)言實(shí)現(xiàn)的迎变,是虛擬機(jī)自身的一部分,它負(fù)責(zé)將 <JAVA_HOME>/lib路徑下的核心類(lèi)庫(kù)或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中飘言,注意由于虛擬機(jī)是按照文件名識(shí)別加載jar包的衣形,如rt.jar,如果文件名不被虛擬機(jī)識(shí)別姿鸿,即使把jar包丟到lib目錄下也是沒(méi)有作用的(出于安全考慮谆吴,Bootstrap啟動(dòng)類(lèi)加載器只加載包名為java、javax苛预、sun等開(kāi)頭的類(lèi))句狼。

擴(kuò)展(Extension)類(lèi)加載器

擴(kuò)展類(lèi)加載器是指Sun公司實(shí)現(xiàn)的sun.misc.Launcher$ExtClassLoader類(lèi),由Java語(yǔ)言實(shí)現(xiàn)的碟渺,是Launcher的靜態(tài)內(nèi)部類(lèi)鲜锚,它負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類(lèi)加載器苫拍。

應(yīng)用程序類(lèi)(Application)類(lèi)加載器

也稱(chēng)應(yīng)用程序加載器是指Sun公司實(shí)現(xiàn)的sun.misc.Launcher$AppClassLoader芜繁。它負(fù)責(zé)加載系統(tǒng)類(lèi)路徑j(luò)ava -classpath或-D java.class.path 指定路徑下的類(lèi)庫(kù),也就是我們經(jīng)常用到的classpath路徑绒极,開(kāi)發(fā)者可以直接使用系統(tǒng)類(lèi)加載器骏令,一般情況下該類(lèi)加載是程序中默認(rèn)的類(lèi)加載器,通過(guò)ClassLoader#getSystemClassLoader()方法可以獲取到該類(lèi)加載器垄提。

自定義(User)類(lèi)加載器

JVM提供的類(lèi)加載器只能加載指定目錄的類(lèi)(jar和class)榔袋,如果我們想從其他地方甚至網(wǎng)絡(luò)上獲取class文件周拐,就需要自定義類(lèi)加載器來(lái)實(shí)現(xiàn),自定義類(lèi)加載器主要都是通過(guò)繼承ClassLoader或者它的子類(lèi)來(lái)實(shí)現(xiàn)凰兑,但無(wú)論是通過(guò)繼承ClassLoader還是它的子類(lèi)妥粟,最終自定義類(lèi)加載器的父加載器都是應(yīng)用程序類(lèi)加載器,因?yàn)椴还苷{(diào)用哪個(gè)父類(lèi)加載器吏够,創(chuàng)建的對(duì)象都必須最終調(diào)用java.lang.ClassLoader.getSystemClassLoader()作為父加載器勾给,getSystemClassLoader()方法的返回值是sun.misc.Launcher.AppClassLoader即應(yīng)用程序類(lèi)加載器。

??????? 在Java的日常應(yīng)用程序開(kāi)發(fā)中锅知,類(lèi)的加載幾乎是由上述3種類(lèi)加載器相互配合執(zhí)行的播急,在必要時(shí),我們還可以自定義類(lèi)加載器售睹,需要注意的是桩警,Java虛擬機(jī)對(duì)class文件采用的是按需加載的方式,也就是說(shuō)當(dāng)需要使用該類(lèi)時(shí)才會(huì)將它的class文件加載到內(nèi)存生成class對(duì)象昌妹,而且加載某個(gè)類(lèi)的class文件時(shí)捶枢,Java虛擬機(jī)采用的是雙親委派模式即把請(qǐng)求交由父類(lèi)處理,它一種任務(wù)委派模式捺宗,下面我們進(jìn)一步了解它柱蟀。

雙親委派模式工作原理

雙親委派模式要求除了頂層的啟動(dòng)類(lèi)加載器外,其余的類(lèi)加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器蚜厉,請(qǐng)注意雙親委派模式中的父子關(guān)系并非通常所說(shuō)的類(lèi)繼承關(guān)系,而是采用組合關(guān)系來(lái)復(fù)用父類(lèi)加載器的相關(guān)代碼畜眨,類(lèi)加載器間的關(guān)系如下:

工作過(guò)程:

一個(gè)類(lèi)加載器首先將類(lèi)加載請(qǐng)求傳送到父類(lèi)加載器昼牛,只有當(dāng)父類(lèi)加載器無(wú)法完成類(lèi)加載請(qǐng)求時(shí)才嘗試加載。

雙親委派模式是在Java 1.2后引入的康聂,其工作原理的是贰健,如果一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求,它并不會(huì)自己先去加載恬汁,而是把這個(gè)請(qǐng)求委托給父類(lèi)的加載器去執(zhí)行伶椿,如果父類(lèi)加載器還存在其父類(lèi)加載器,則進(jìn)一步向上委托氓侧,依次遞歸脊另,請(qǐng)求最終將到達(dá)頂層的啟動(dòng)類(lèi)加載器,如果父類(lèi)加載器可以完成類(lèi)加載任務(wù)约巷,就成功返回偎痛,倘若父類(lèi)加載器無(wú)法完成此加載任務(wù),子加載器才會(huì)嘗試自己去加載独郎,這就是雙親委派模式踩麦,即每個(gè)兒子都很懶枚赡,每次有活就丟給父親去干,直到父親說(shuō)這件事我也干不了時(shí)谓谦,兒子自己想辦法去完成贫橙,這不就是傳說(shuō)中的實(shí)力坑爹啊反粥?那么采用這種模式有啥用呢卢肃?

//雙親委派模型的工作過(guò)程源碼

protected synchronized Class loadClass(String name,boolean resolve)

throws ClassNotFoundException

{

??? // First, check if theclass has already been loaded

??? Class c =findLoadedClass(name);

??? if (c == null) {

??????? try {

??????????? if (parent !=null) {

??????????????? c =parent.loadClass(name, false);

??????????? } else {

??????????????? c =findBootstrapClassOrNull(name);

??????????? }

??????? } catch(ClassNotFoundException e) {

??????????? //ClassNotFoundException thrown if class not found

??????????? // from thenon-null parent class loader

??????????? //父類(lèi)加載器無(wú)法完成類(lèi)加載請(qǐng)求

??????? }

??????? if (c == null) {

??????????? // If still notfound, then invoke findClass in order to find the class

??????????? //子加載器進(jìn)行類(lèi)加載

??????????? c =findClass(name);

??????? }

??? }

??? if (resolve) {

??????? //判斷是否需要鏈接過(guò)程,參數(shù)傳入

??????? resolveClass(c);

??? }

??? return c;

}

雙親委派模型的工作過(guò)程如下:

(1)當(dāng)前類(lèi)加載器從自己已經(jīng)加載的類(lèi)中查詢(xún)是否此類(lèi)已經(jīng)加載星压,如果已經(jīng)加載則直接返回原來(lái)已經(jīng)加載的類(lèi)践剂。

(2)如果沒(méi)有找到,就去委托父類(lèi)加載器去加載(如代碼c = parent.loadClass(name, false)所示)娜膘。父類(lèi)加載器也會(huì)采用同樣的策略逊脯,查看自己已經(jīng)加載過(guò)的類(lèi)中是否包含這個(gè)類(lèi),有就返回竣贪,沒(méi)有就委托父類(lèi)的父類(lèi)去加載军洼,一直到啟動(dòng)類(lèi)加載器。因?yàn)槿绻讣虞d器為空了演怎,就代表使用啟動(dòng)類(lèi)加載器作為父加載器去加載匕争。

(3)如果啟動(dòng)類(lèi)加載器加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class)爷耀,會(huì)使用拓展類(lèi)加載器來(lái)嘗試加載甘桑,繼續(xù)失敗則會(huì)使用AppClassLoader來(lái)加載,繼續(xù)失敗則會(huì)拋出一個(gè)異常ClassNotFoundException歹叮,然后再調(diào)用當(dāng)前加載器的findClass()方法進(jìn)行加載跑杭。

雙親委派模式優(yōu)勢(shì)

采用雙親委派模式的好處是:

1)Java類(lèi)隨著它的類(lèi)加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,通過(guò)這種層級(jí)關(guān)可以避免類(lèi)的重復(fù)加載咆耿,當(dāng)父親已經(jīng)加載了該類(lèi)時(shí)德谅,就沒(méi)有必要子ClassLoader再加載一次。

2)其次是考慮到安全因素萨螺,java核心api中定義類(lèi)型不會(huì)被隨意替換窄做,假設(shè)通過(guò)網(wǎng)絡(luò)傳遞一個(gè)名為java.lang.Integer的類(lèi),通過(guò)雙親委托模式傳遞到啟動(dòng)類(lèi)加載器慰技,而啟動(dòng)類(lèi)加載器在核心Java API發(fā)現(xiàn)這個(gè)名字的類(lèi)椭盏,發(fā)現(xiàn)該類(lèi)已被加載,并不會(huì)重新加載網(wǎng)絡(luò)傳遞的過(guò)來(lái)的java.lang.Integer惹盼,而直接返回已加載過(guò)的Integer.class庸汗,這樣便可以防止核心API庫(kù)被隨意篡改。

可能你會(huì)想手报,如果我們?cè)赾lasspath路徑下自定義一個(gè)名為java.lang.SingleInterge類(lèi)(該類(lèi)是胡編的)呢蚯舱?該類(lèi)并不存在java.lang中改化,經(jīng)過(guò)雙親委托模式,傳遞到啟動(dòng)類(lèi)加載器中枉昏,由于父類(lèi)加載器路徑下并沒(méi)有該類(lèi)陈肛,所以不會(huì)加載,將反向委托給子類(lèi)加載器加載兄裂,最終會(huì)通過(guò)系統(tǒng)類(lèi)加載器加載該類(lèi)句旱。但是這樣做是不允許,因?yàn)閖ava.lang是核心API包晰奖,需要訪(fǎng)問(wèn)權(quán)限谈撒,強(qiáng)制加載將會(huì)報(bào)出如下異常:java.lang.SecurityException:Prohibited package name: java.lang。

類(lèi)加載器間的關(guān)系

我們進(jìn)一步了解類(lèi)加載器間的關(guān)系(并非指繼承關(guān)系)匾南,主要可以分為以下4點(diǎn):

啟動(dòng)類(lèi)加載器啃匿,由C++實(shí)現(xiàn),沒(méi)有父類(lèi)蛆楞。

拓展類(lèi)加載器(ExtClassLoader)溯乒,由Java語(yǔ)言實(shí)現(xiàn),父類(lèi)加載器為null

系統(tǒng)類(lèi)加載器(AppClassLoader)豹爹,由Java語(yǔ)言實(shí)現(xiàn)裆悄,父類(lèi)加載器為ExtClassLoader

自定義類(lèi)加載器,父類(lèi)加載器肯定為AppClassLoader臂聋。


類(lèi)與類(lèi)加載器

在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類(lèi)對(duì)象存在兩個(gè)必要條件:

* 類(lèi)的完整類(lèi)名必須一致光稼,包括包名。

* 加載這個(gè)類(lèi)的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同孩等。

顯示加載與隱式加載

所謂class文件的顯示加載與隱式加載的方式是指JVM加載class文件到內(nèi)存的方式钟哥,顯示加載指的是在代碼中通過(guò)調(diào)用ClassLoader加載class對(duì)象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加載class對(duì)象瞎访。而隱式加載則是不直接在代碼中調(diào)用ClassLoader的方法加載class對(duì)象,而是通過(guò)虛擬機(jī)自動(dòng)加載到內(nèi)存中吁恍,如在加載某個(gè)類(lèi)的class文件時(shí)扒秸,該類(lèi)的class文件中引用了另外一個(gè)類(lèi)的對(duì)象,此時(shí)額外引用的類(lèi)將通過(guò)JVM自動(dòng)加載到內(nèi)存中冀瓦。在日常開(kāi)發(fā)以上兩種方式一般會(huì)混合使用伴奥,這里我們知道有這么回事即可。

編寫(xiě)自己的類(lèi)加載器

通過(guò)前面的分析可知翼闽,實(shí)現(xiàn)自定義類(lèi)加載器需要繼承ClassLoader或者URLClassLoader拾徙,繼承ClassLoader則需要自己重寫(xiě)findClass()方法并編寫(xiě)加載邏輯,繼承URLClassLoader則可以省去編寫(xiě)findClass()方法以及class文件加載轉(zhuǎn)換成字節(jié)碼流的代碼感局。那么編寫(xiě)自定義類(lèi)加載器的意義何在呢尼啡?

* 當(dāng)class文件不在ClassPath路徑下暂衡,默認(rèn)系統(tǒng)類(lèi)加載器無(wú)法找到該class文件,在這種情況下我們需要實(shí)現(xiàn)一個(gè)自定義的ClassLoader來(lái)加載特定路徑下的class文件生成class對(duì)象崖瞭。

* 當(dāng)一個(gè)class文件是通過(guò)網(wǎng)絡(luò)傳輸并且可能會(huì)進(jìn)行相應(yīng)的加密操作時(shí)狂巢,需要先對(duì)class文件進(jìn)行相應(yīng)的解密后再加載到JVM內(nèi)存中,這種情況下也需要編寫(xiě)自定義的ClassLoader并實(shí)現(xiàn)相應(yīng)的邏輯书聚。

* 以上兩種情況在實(shí)際中的綜合運(yùn)用:比如你的應(yīng)用需要通過(guò)網(wǎng)絡(luò)來(lái)傳輸 Java 類(lèi)的字節(jié)碼唧领,為了安全性,這些字節(jié)碼經(jīng)過(guò)了加密處理雌续。這個(gè)時(shí)候你就需要自定義類(lèi)加載器來(lái)從某個(gè)網(wǎng)絡(luò)地址上讀取加密后的字節(jié)代碼斩个,接著進(jìn)行解密和驗(yàn)證,最后定義出在Java虛擬機(jī)中運(yùn)行的類(lèi)驯杜。

* 當(dāng)需要實(shí)現(xiàn)熱部署功能時(shí)(一個(gè)class文件通過(guò)不同的類(lèi)加載器產(chǎn)生不同class對(duì)象從而實(shí)現(xiàn)熱部署功能)受啥,需要實(shí)現(xiàn)自定義ClassLoader的邏輯。

自定義類(lèi)加載器

(1)從上面源碼看出艇肴,調(diào)用loadClass時(shí)會(huì)先根據(jù)委派模型在父加載器中加載腔呜,如果加載失敗,則會(huì)調(diào)用當(dāng)前加載器的findClass來(lái)完成加載再悼。

(2)因此我們自定義的類(lèi)加載器只需要繼承ClassLoader核畴,并覆蓋findClass方法,下面是一個(gè)實(shí)際例子冲九,在該例中我們用自定義的類(lèi)加載器去加載我們事先準(zhǔn)備好的class文件谤草。


自定義一個(gè)People.java類(lèi)做例子

public class People {

??? //該類(lèi)寫(xiě)在記事本里,在用javac命令行編譯成class文件莺奸,放在d盤(pán)根目錄下

??? private String name;

??? public People() {}

??? public People(Stringname) {

???????? this.name = name;

??? }

??? public String getName(){

???????? return name;

??? }

??? public voidsetName(String name) {

???????? this.name = name;

??? }

??? public String toString(){

???????? return "I am apeople, my name is " + name;

??? }

}


自定義類(lèi)加載器

自定義一個(gè)類(lèi)加載器丑孩,需要繼承ClassLoader類(lèi),并實(shí)現(xiàn)findClass方法灭贷。其中defineClass方法可以把二進(jìn)制流字節(jié)組成的文件轉(zhuǎn)換為一個(gè)java.lang.Class(只要二進(jìn)制字節(jié)流的內(nèi)容符合Class文件規(guī)范)温学。

public class MyClassLoader extends ClassLoader

{

??? public MyClassLoader()

??? {

??? }

??? public MyClassLoader(ClassLoader parent)

??? {

??????? super(parent);

??? }

??? protected ClassfindClass(String name) throws ClassNotFoundException

??? {

??????? File file = newFile("D:/People.class");

??????? try{

??????????? byte[] bytes = getClassBytes(file);

??????????? //defineClass方法可以把二進(jìn)制流字節(jié)組成的文件轉(zhuǎn)換為一個(gè)java.lang.Class

??????????? Class c = this.defineClass(name, bytes, 0, bytes.length);

??????????? return c;

??????? }

??????? catch (Exception e)

??????? {

???????????e.printStackTrace();

??????? }

??????? return super.findClass(name);

??? }

??? private byte[] getClassBytes(File file) throws Exception

??? {

??????? //這里要讀入.class的字節(jié),因此要使用字節(jié)流

??????? FileInputStream fis= new FileInputStream(file);

??????? FileChannel fc =fis.getChannel();

???????ByteArrayOutputStream baos = new ByteArrayOutputStream();

??????? WritableByteChannel wbc = Channels.newChannel(baos);

??????? ByteBuffer by = ByteBuffer.allocate(1024);


??????? while (true){

??????????? int i =fc.read(by);

??????????? if (i == 0 || i== -1)

??????????? break;

??????????? by.flip();

??????????? wbc.write(by);

??????????? by.clear();

??????? }

??????? fis.close();

??????? returnbaos.toByteArray();

??? }


??? public static voidmain(String[] args)

??? {

??????? MyClassLoader mcl =new MyClassLoader();

??????? Class?clazz;

??????? try

??????? {

??????????? clazz =Class.forName("People", true, mcl);

??????????? Object obj;

??????????? obj =clazz.newInstance();

???????????System.out.println(obj);

???????????System.out.println(obj.getClass().getClassLoader());//打印出我們的自定義類(lèi)加載器

??????? }

??????? catch(ClassNotFoundException | InstantiationException | IllegalAccessException e)

??????? {

???????????e.printStackTrace();

??????? }

??? }

}

運(yùn)行結(jié)果:

I am a

people, my name is null

com.huawei.monitor.calcsvc.utils.MyClassLoader@15db9742


Tomcat類(lèi)加載機(jī)制:

Tomcat6.0:

當(dāng)應(yīng)用需要到某個(gè)類(lèi)時(shí)甚疟,則會(huì)按照下面的順序進(jìn)行類(lèi)加載:

  1)使用bootstrap引導(dǎo)類(lèi)加載器加載

  2)使用system系統(tǒng)類(lèi)加載器加載

  3)使用應(yīng)用類(lèi)加載器在WEB-INF/classes中加載

  4)使用應(yīng)用類(lèi)加載器在WEB-INF/lib中加載

? ? ? ?5)使用common類(lèi)加載器在CATALINA_HOME/lib中加載

Tomcat7.0+:

加載類(lèi)或資源時(shí)仗岖,要查看的倉(cāng)庫(kù)及其順序如下:

1)JVM 的 Bootstrap 類(lèi)

2)應(yīng)用類(lèi)加載器加載Web 應(yīng)用的/WEB-INF/classes 類(lèi)

3)應(yīng)用類(lèi)加載器加載Web 應(yīng)用的/WEB-INF/lib/*.jar 類(lèi)

4)System 類(lèi)加載器的類(lèi)

5)Common 類(lèi)加載器的類(lèi)


如果 Web 應(yīng)用類(lèi)加載器配置有 <Loader delegate="true"/>,則順序變?yōu)椋?/p>

1)JVM 的 Bootstrap 類(lèi)

2)System 類(lèi)加載器的類(lèi)

3)Common 類(lèi)加載器的類(lèi)

4)應(yīng)用類(lèi)加載器加載Web 應(yīng)用的/WEB-INF/classes 類(lèi)

5)應(yīng)用類(lèi)加載器加載Web 應(yīng)用的/WEB-INF/lib/*.jar 類(lèi)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末览妖,一起剝皮案震驚了整個(gè)濱河市轧拄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讽膏,老刑警劉巖檩电,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡俐末,警方通過(guò)查閱死者的電腦和手機(jī)料按,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鹅搪,“玉大人站绪,你說(shuō)我怎么就攤上這事±鍪粒” “怎么了恢准?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)甫题。 經(jīng)常有香客問(wèn)我馁筐,道長(zhǎng),這世上最難降的妖魔是什么坠非? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任敏沉,我火速辦了婚禮,結(jié)果婚禮上炎码,老公的妹妹穿的比我還像新娘盟迟。我一直安慰自己,他們只是感情好潦闲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布攒菠。 她就那樣靜靜地躺著,像睡著了一般歉闰。 火紅的嫁衣襯著肌膚如雪辖众。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天和敬,我揣著相機(jī)與錄音凹炸,去河邊找鬼。 笑死昼弟,一個(gè)胖子當(dāng)著我的面吹牛啤它,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舱痘,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚕键,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了衰粹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笆怠,失蹤者是張志新(化名)和其女友劉穎铝耻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓢捉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年频丘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泡态。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搂漠,死狀恐怖某弦,靈堂內(nèi)的尸體忽然破棺而出桐汤,到底是詐尸還是另有隱情,我是刑警寧澤靶壮,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布怔毛,位于F島的核電站,受9級(jí)特大地震影響腾降,放射性物質(zhì)發(fā)生泄漏拣度。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一螃壤、第九天 我趴在偏房一處隱蔽的房頂上張望抗果。 院中可真熱鬧,春花似錦奸晴、人聲如沸冤馏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宿接。三九已至,卻和暖如春辕录,著一層夾襖步出監(jiān)牢的瞬間睦霎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工走诞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留副女,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓蚣旱,卻偏偏與公主長(zhǎng)得像碑幅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塞绿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容