JVM:类加载机制之类加载器
JVM設(shè)計(jì)者把類加載階段中的“通過'類全名'來獲取定義此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。
?1.類與類加載器
對(duì)于任何一個(gè)類,都需要由加載它的類加載器和這個(gè)類來確立其在JVM中的唯一性。也就是說,兩個(gè)類來源于同一個(gè)Class文件,并且被同一個(gè)類加載器加載,這兩個(gè)類才相等。
2.雙親委派模型
先找后詢問,存在就不用向上詢問
從虛擬機(jī)的角度來說,只存在兩種不同的類加載器:
一種是啟動(dòng)類加載器(Bootstrap ClassLoader),該類加載器使用C++語言實(shí)現(xiàn),屬于JVM自身的一部分。
一種就是所有其它的類加載器,這些類加載器是由Java語言實(shí)現(xiàn),獨(dú)立于JVM外部,并且全部繼承自抽java.lang.ClassLoader。
從Java開發(fā)人員的角度來看,大部分Java程序一般會(huì)使用到以下三種系統(tǒng)提供的類加載器:
1)啟動(dòng)類加載器(BootstrapClassLoader):負(fù)責(zé)加載JAVA_HOME\lib目錄中并且能被虛擬機(jī)識(shí)別的類庫到JVM內(nèi)存中,如果名稱不符合的類庫即使放在lib目錄中也不會(huì)被加載。該類加載器無法被Java程序直接引用。
2)擴(kuò)展類加載器(ExtensionClassLoader):該加載器主要是負(fù)責(zé)加載JAVA_HOME\lib\,該加載器可以被開發(fā)者直接使用。
3)應(yīng)用程序類加載器(ApplicationClassLoader):該類加載器也稱為系統(tǒng)類加載器,它負(fù)責(zé)加載用戶類路徑(Classpath)上所指定的類庫,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。(開發(fā)者寫的類默認(rèn)都是應(yīng)用程序類加載器加載的)
我們的應(yīng)用程序都是由這三類加載器互相配合進(jìn)行加載的,我們也可以加入自己定義的類加載器。這些類加載器之間的關(guān)系如下圖所示:
?
如上圖所示的類加載器之間的這種層次關(guān)系,就稱為類加載器的雙親委派模型(Parent Delegation Model)。
該模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關(guān)系來實(shí)現(xiàn),而是通過組合(Composition)關(guān)系來復(fù)用父加載器的代碼。
雙親委派模型的工作過程為:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的加載器都是如此,因此所有的類加載請(qǐng)求都會(huì)傳給頂層的啟動(dòng)類加載器,當(dāng)父加載器反饋?zhàn)约簾o法完成該加載請(qǐng)求(該加載器的搜索范圍中沒有找到對(duì)應(yīng)的類)時(shí),再自頂向下嘗試加載,如果都沒有加載到,那么這個(gè)類加載器才會(huì)嘗試自己去加載。
因此請(qǐng)求詢問是自底向上!類加載操作是自頂向下!
1.? 在rt.jar包中的java.lang.ClassLoader類中,我們可以查看類加載實(shí)現(xiàn)過程的代碼,具體源碼如下:protected?synchronized?Class?loadClass(String?name,?boolean?resolve)??
2.? ????????throws?ClassNotFoundException?{??
3.? ????//?首先檢查該name指定的class是否有被加載??
4.? ????Class?c?=?findLoadedClass(name);??
5.? ????if?(c?==?null)?{??
6.? ????????try?{??
7.? ????????????if?(parent?!=?null)?{??
8.? ????????????????//?如果parent不為null,則調(diào)用parent的loadClass進(jìn)行加載??
9.? ????????????????c?=?parent.loadClass(name,?false);??
10. ????????????}?else?{??
11. ????????????????//?parent為null,則調(diào)用BootstrapClassLoader進(jìn)行加載??
12. ????????????????c?=?findBootstrapClass0(name);??
13. ????????????}??
14. ????????}?catch?(ClassNotFoundException?e)?{??
15. ????????????//?如果仍然無法加載成功,則調(diào)用自身的findClass進(jìn)行加載??
16. ????????????c?=?findClass(name);??
17. ????????}??
18. ????}??
19. ????if?(resolve)?{??
20. ????????resolveClass(c);??
21. ????}??
22. ????return?c;??
23. }??
通過上面代碼可以看出,雙親委派模型是通過loadClass()方法來實(shí)現(xiàn)的,根據(jù)代碼以及代碼中的注釋可以很清楚地了解整個(gè)過程其實(shí)非常簡(jiǎn)單:先檢查是否已經(jīng)被加載過,如果沒有則調(diào)用父加載器的loadClass()方法,如果父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載器加載失敗,則先拋出ClassNotFoundException,然后再調(diào)用自己的findClass()方法進(jìn)行加載。
3.自定義類加載器
若要實(shí)現(xiàn)自定義類加載器,只需要繼承java.lang.ClassLoader 類,并且重寫其findClass()方法即可。java.lang.ClassLoader 類的基本職責(zé)就是根據(jù)一個(gè)指定的類的名稱,找到或者生成其對(duì)應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè) Java 類,即 java.lang.Class 類的一個(gè)實(shí)例。除此之外,ClassLoader 還負(fù)責(zé)加載 Java 應(yīng)用所需的資源,如圖像文件和配置文件等,ClassLoader 中與加載類相關(guān)的方法如下:
| ?方法 | 說明 |
| getParent() | 返回該類加載器的父類加載器。 |
| loadClass(String name) | 加載名稱為?name的類,返回的結(jié)果是?java.lang.Class類的實(shí)例。 |
| findClass(String name) | 查找名稱為?name的類,返回的結(jié)果是?java.lang.Class類的實(shí)例。 |
| findLoadedClass(String name) | 查找名稱為?name的已經(jīng)被加載過的類,返回的結(jié)果是?java.lang.Class類的實(shí)例。 |
| defineClass(String name, byte[] b, int off, int len) | 把字節(jié)數(shù)組?b中的內(nèi)容轉(zhuǎn)換成 Java 類,返回的結(jié)果是?java.lang.Class類的實(shí)例。這個(gè)方法被聲明為?final的。 |
| resolveClass(Class<?> c) | 鏈接指定的 Java 類。 |
?
注意:在JDK1.2之前,類加載尚未引入雙親委派模式,因此實(shí)現(xiàn)自定義類加載器時(shí)常常重寫loadClass方法,提供雙親委派邏輯,從JDK1.2之后,雙親委派模式已經(jīng)被引入到類加載體系中,自定義類加載器時(shí)不需要在自己寫雙親委派的邏輯,因此不鼓勵(lì)重寫loadClass方法,而推薦重寫findClass方法。
?
雙親委派模式優(yōu)勢(shì)
?
采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,通過這種層級(jí)關(guān)可以避免類的重復(fù)加載,當(dāng)父親已經(jīng)加載了該類時(shí),就沒有必要子ClassLoader再加載一次。
其次是考慮到安全因素,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)出如下異常
幾點(diǎn)思考
? ? ?0.頂層的ClassLoder無法加載底層ClassLoder的類!!!
? ? ??
? ? ? 解決方案:
源碼
Java虛擬機(jī)的第一個(gè)類加載器是Bootstrap,這個(gè)加載器很特殊,它不是Java類,因此它不需要被別人加載,它嵌套在Java虛擬機(jī)內(nèi)核里面,也就是JVM啟動(dòng)的時(shí)候Bootstrap就已經(jīng)啟動(dòng),它是用C++寫的二進(jìn)制代碼(不是字節(jié)碼),它可以去加載別的類。
這也是我們?cè)跍y(cè)試時(shí)為什么發(fā)現(xiàn)System.class.getClassLoader()結(jié)果為null的原因,這并不表示System這個(gè)類沒有類加載器,而是它的加載器比較特殊,是BootstrapClassLoader,由于它不是Java類,因此獲得它的引用肯定返回null。
委托機(jī)制具體含義?
當(dāng)Java虛擬機(jī)要加載一個(gè)類時(shí),到底派出哪個(gè)類加載器去加載呢?
- 首先當(dāng)前線程的類加載器去加載線程中的第一個(gè)類(假設(shè)為類A)。?
注:當(dāng)前線程的類加載器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設(shè)置類加載器。 - 如果類A中引用了類B,Java虛擬機(jī)將使用加載類A的類加載器去加載類B。
- 還可以直接調(diào)用ClassLoader.loadClass()方法來指定某個(gè)類加載器去加載某個(gè)類。
委托機(jī)制的意義 — 防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼?
比如兩個(gè)類A和類B都要加載System類:
- 如果不用委托而是自己加載自己的,那么類A就會(huì)加載一份System字節(jié)碼,然后類B又會(huì)加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
- 如果使用委托機(jī)制,會(huì)遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時(shí)類B也要加載System,也從Bootstrap開始,此時(shí)Bootstrap發(fā)現(xiàn)已經(jīng)加載過了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了。
?破壞雙親委派模型?基于反射
假設(shè)寫兩個(gè)一摸一樣的類(類名相同),一個(gè)放置在classpath下,一個(gè)放置在任意目錄下但是用-Xbootclasspath參數(shù)與啟動(dòng)類加載器進(jìn)行綁定!毫無疑問,當(dāng)執(zhí)行main函數(shù)的時(shí)候,一定會(huì)執(zhí)行任意目錄下的那個(gè)類!這是雙親委派模型的實(shí)現(xiàn)!
但是能否強(qiáng)制執(zhí)行classpath下的那個(gè)類呢?答案是可以的。
通過反射機(jī)制將classpath下的那個(gè)類提前加載到classpath下,那么當(dāng)執(zhí)行main函數(shù)的時(shí)候,由于AppClassLoder已經(jīng)加載了這個(gè)類,既然已經(jīng)找到了就不必向上詢問了,也就可以直接執(zhí)行classpath下的類了!
?
實(shí)例:
? ? ? ??
上面拋出異常是由于:我們重載修改了classLoder方法使得使用自定義的類加載器進(jìn)行加載,但是我們知道在任何類進(jìn)行加載之前都需要將父類加載進(jìn)JVM!(jvm類加載過程中有談到),但是呢,由于我這個(gè)類沒有繼承任何類,因此他的父類就是Object類,而Object類存在于rt.jar中,由BootClassLoader加載,因此由于找不到Object類所以拋出異常!!!因此Object類最后由父類加載器進(jìn)行加載。而demoA是由自定義的OrderClassLoader進(jìn)行加載的!!!!
熱替換
?
總結(jié)
以上是生活随笔為你收集整理的JVM:类加载机制之类加载器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: websocket导致spring bo
- 下一篇: JVM:类加载机制之类加载过程