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

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

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

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

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

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

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

1、通過一個(gè)類的全限定名獲取描述此類的二進(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è)類的java.lang.Class對(duì)象,作為訪問方法區(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)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值,使用的是方法區(qū)的內(nèi)存。

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

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

初始值一般為 0 值,例如下面的類變量 value 被初始化為 0 而不是 123。

public static int value = 123;

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

public static final int value = 123;

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

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

分為類或接口的解析,字段解析,類方法解析,接口方法解析。

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

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

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

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

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

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

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

????????????????? 基類的static域加載

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

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

? ? ? ? ? ? ? ? ?子類的static域加載?????????????????? ?????

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

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

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

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

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

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

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

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

???????????? 子類的成員變量初始化???????????????????????????? ?????

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

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

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


類初始化場景:

1,主動(dòng)引用

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

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

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

* 初始化一個(gè)類的時(shí)候,父類還沒有初始化,會(huì)事先初始化父類;

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

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

2,被動(dòng)引用

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

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

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

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ù)組,即通過數(shù)組定義來引用類,不會(huì)觸發(fā)該類的初始化。

public class Init{?

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

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

??? }?

}?

無輸出,說明沒有觸發(fā)類Parent的初始化,但是這段代碼做了什么?先看看生成的字節(jié)碼指令

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

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

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

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

4)通過類名獲取Class對(duì)象,不會(huì)觸發(fā)類的初始化。

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,所以通過Dog.class并不會(huì)觸發(fā)Dog類的初始化動(dòng)作。

5)通過Class.forName加載指定類時(shí),如果指定參數(shù)initialize為false時(shí),也不會(huì)觸發(fā)類初始化,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(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)通過ClassLoader默認(rèn)的loadClass方法,也不會(huì)觸發(fā)初始化動(dòng)作

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


JVM類加載器

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

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

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

擴(kuò)展(Extension)類加載器

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

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

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

自定義(User)類加載器

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

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

雙親委派模式工作原理

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

工作過程:

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

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

//雙親委派模型的工作過程源碼

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

??????????? //父類加載器無法完成類加載請(qǐng)求

??????? }

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

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

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

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

??????? }

??? }

??? if (resolve) {

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

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

??? }

??? return c;

}

雙親委派模型的工作過程如下:

(1)當(dāng)前類加載器從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類。

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

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

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

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

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

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

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

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

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

啟動(dòng)類加載器,由C++實(shí)現(xiàn),沒有父類。

拓展類加載器(ExtClassLoader),由Java語言實(shí)現(xiàn),父類加載器為null

系統(tǒng)類加載器(AppClassLoader),由Java語言實(shí)現(xiàn),父類加載器為ExtClassLoader

自定義類加載器,父類加載器肯定為AppClassLoader。


類與類加載器

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

* 類的完整類名必須一致,包括包名。

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

顯示加載與隱式加載

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

編寫自己的類加載器

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

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

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

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

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

自定義類加載器

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

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


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

public class People {

??? //該類寫在記事本里,在用javac命令行編譯成class文件,放在d盤根目錄下

??? 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;

??? }

}


自定義類加載器

自定義一個(gè)類加載器,需要繼承ClassLoader類,并實(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());//打印出我們的自定義類加載器

??????? }

??????? 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類加載機(jī)制:

Tomcat6.0:

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

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

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

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

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

? ? ? ?5)使用common類加載器在CATALINA_HOME/lib中加載

Tomcat7.0+:

加載類或資源時(shí),要查看的倉庫及其順序如下:

1)JVM 的 Bootstrap 類

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

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

4)System 類加載器的類

5)Common 類加載器的類


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

1)JVM 的 Bootstrap 類

2)System 類加載器的類

3)Common 類加載器的類

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容