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