1. 程式人生 > >JVM的藝術—類載入器篇(二)

JVM的藝術—類載入器篇(二)

分享是價值的傳遞,喜歡就點個贊 #### **引言** 今天我們繼續來深入的剖析類載入器的內容。上節課我們講了類載入器的基本內容,沒看過的小夥伴請加關注。今天我們繼續。 ## 什麼是定義類載入器和初始化類載入器? - 定義類載入器:假設我們的某一個類是由ExtClassLoader載入的,那麼ExtClassLoader稱為該類的定義類載入器 - 初始化載入器:能夠返回Class物件引用的都叫做該類的初始類載入器,比如類A是由我們的ExtClassLoader載入,那麼 ExtClassLoader是該類的定義類載入器,也是該類的初始類載入器,而我們的AppClassLoader也能返回我們A類的引用 那麼AppClassLoader也是該類的初始類載入器。 ## 什麼是類載入器的雙親委派模型? 上篇文章我們提到了類載入器的雙親委派模型,也可以稱為雙親委託模型。今天這篇文章我們就來把這個概念給講明白。 概念:用一種簡單的方式去描述雙親委託的概念。可以分為兩個部分去理解 > ##### 1委託: > > jvm載入類的時候是通過雙親委派的方式去載入,自下而上的去委託。 > > 自定義類載入器需要載入類時,先委託應用類載入器去載入,然後應用類載入器又向擴充套件類載入器去委託,擴充套件類載入器在向啟動類載入器去委託。 > > 如果啟動類載入器不能載入該類。那麼就向下載入 > ##### 2載入: > > jvm載入類的時候是通過雙親委派的方式去載入委託,但是載入的時候是由上向下去載入的,當委託到最頂層啟動類載入器的時候,無法在向上委託,那麼 > > 啟動類載入器就開始嘗試去載入這個類,啟動類載入器載入不了就向下交給擴充套件類載入器去載入,擴充套件類載入器載入不了就繼續向下委託交給應用類載入器 > > 去載入,以此類推。 如果文字描述你還不清楚什麼是雙親委託機制,那麼我畫了一幅圖可以更清楚類載入的過程。如下: ![](https://img2020.cnblogs.com/other/1187061/202010/1187061-20201031220833166-850903438.png) 通過上圖,我們知道更能清楚的知道,雙親委託模型的工作機制,用一句簡單的話說,就是需要載入一個類的時候,向上委託,向下載入。 > 注意:在雙親委派機制中,各個載入器按照父子關係形成樹型結構,除了根載入器以外,每一個載入器有且只有一個父載入器。 接下來,我也從jdk底層原始碼的角度給大家畫了一張類載入的主要過程,圖如下: ![](https://img2020.cnblogs.com/other/1187061/202010/1187061-20201031220834231-667359566.png) 以上就是類載入器載入一個類的重要過程步驟。希望各位小夥兒可以結合原始碼的方式,仔細再研究一下。其實還挺好理解的。 下面咱們再說說,java採用雙親委託的方式去載入類,這樣做的好處是什麼呢? - ### 雙親委派模型的好處 總所周知:java.lang.object類是所有類的父類,所以我們程式在執行期間會把java.lang.object類載入到記憶體中,假如java.lang.object類 能夠被我們自定義類載入器去載入的話,那麼jvm中就會存在多份Object的Class物件,而且這些Class物件是不相容的。 所以雙親委派模型可以保證java核心類庫下的型別的安全。 藉助雙親委派模型,我們java核心類庫的類必須是由我們的啟動類載入器載入的,這樣可以確保我們核心類庫只會在jvm中存在一份 這就不會給自定義類載入器去載入我們核心類庫的類。 根據我們的演示案例,一個class可以由多個類載入器去載入,同時可以在jvm記憶體中存在多個不同版本的Class物件,這些物件是不相容的。 並且是不能相互轉換的。 ### 什麼是全盤委託載入? 解釋:假如我們的Person類是由我們的系統類APP類載入器載入的,而person類所依賴的Dog類也會委託給App系統類進 行載入,這個委託過程也遵循雙親委派模型。程式碼如下 - **person類程式碼中建立Dog例項** public class Person { ```java public Person(){ new Dog(); } ``` } ```java public class Dog { public Dog(){ System.out.println("Dog 的建構函式"); } } ``` - **測試類** ```java public class MainClass02 { public static void main(String[] args) throws Exception { //建立自定義類載入器的一個例項,並且通過構造器指定名稱 Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1"); myClassLoader.setPath("I:\\test\\"); Class classz = myClassLoader.loadClass("com.test.Person"); System.out.println(classz.getClassLoader()); System.out.println(Dog.class.getClassLoader()); } } 執行結果: sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2 Process finished with exit code 0 ``` 從上面的執行結果,我們可以看出,當我們用自定義類載入器去載入我們的Person的時候,根據雙親委託模型,我們的Person並沒有被自定義類載入(Test01ClassLoader)載入,而是被AppClassloader載入成功,同時根據全盤委託規則,我們的Dog類也被AppClassLoader載入了。所以大家一定要記住這個至關重要的結論。為我們後面的學習打下堅實的基礎。 下面我們在看一個例子。我們把類路徑下的Person.class檔案刪除掉,然後再執行一下上面的main函式,看看結果。程式碼如下: ![](https://img2020.cnblogs.com/other/1187061/202010/1187061-20201031220834638-787124842.png) 通過那行結果我們看出,Person類是由我們的自定義類載入器載入的。那為什麼Dog類沒有進行全盤委託的,這是因為雙親委託模型的緣故,我們的類路徑下並沒有Person類,故此AppClassLoader是無法載入我們的路徑I:\\\test\\\下的com.test.Person.class檔案的。所以Person類是由我們自定的類載入器載入的。再看Dog類,由於它的載入要遵循雙親委託模型,因為類路徑下有Dog.class檔案,所以AppClassLoader就可以載入Dog類。故此載入Dog類的ClassLoader是AppClassLoader。寫到這裡,大家對類載入已經有了一個非常深刻的理解。那麼java為什麼使用雙親委託模型的好處我相信已經不言而喻了。那麼下面來說說雙親委託模型,有沒有他的弊端呢,或者說有什麼不好的地方嘛?我們可以打破這種雙親委託的方式去載入類嘛?下面我們來看一個例子。 ### **類載入器的名稱空間** 說到雙親委託模型的弊端,那我就離不開名稱空間的概念。 **類載入器的名稱空間** **是由類載入器本身以及所有父載入器所加載出來的binary name(full class name)組成.** **①:在同一個名稱空間裡,不允許出現二個完全一樣的binary name。** **②:在不同的名稱空間種,可以出現二個相同的binary name。當時二者對應的Class物件是相互不能感知到的,也就是說Class物件的型別是不一樣的。** 解釋:同一個Person.class檔案 被我們的不同的類載入器去載入,那麼我們的jvm記憶體中會生成二個對應的Person的Class物件,而且這二個對應的Class物件是相互不可見的(通過Class物件反射建立的例項物件相互是不能夠相容的不能相互轉型** **③:子載入器的名稱空間中的binary name對應的類中可以訪問 父載入器名稱空間中binary name對應的類,反之不行** 下面準備了一張圖,以便於大家的理解。 ![](https://img2020.cnblogs.com/other/1187061/202010/1187061-20201031220834850-1772156537.png) 上面這張圖就很好的解釋了名稱空間的概念。大家可以再好好的體會一下。 我們光畫圖,光用嘴說並不是一種很有力的證據,就如同我寫在這篇博文的時候所提,我們在學習和掌握某個概念的時候,就必須要拿出有力的證據,來證明自己的猜想或者是觀點,那我們就舉一個例子。來驗證一下我們上面的理論是否正確。程式碼如下: 這是Person類的程式碼。 ```java package com.test; public class Person { public Person() { new Dog(); System.out.println("Dog的classLoader:-->"+ Dog.class.getClassLoader()); } static{ System.out.println("person類被初始化了"); } } ``` 這是Dog類的程式碼。 ```java package com.test; public class Dog { public Dog(){ System.out.println("Dog 的建構函式"); } } ``` 具體的驗證思路是這樣的,首先我們把Person類的Class檔案放到啟動類載入器的載入目錄下(C:\Program Files\Java\jdk1.8.0_144\jre\classes 這是啟動類載入器的載入目錄)來達到Person類交給啟動類載入器載入的目的。 然後呢,我們讓Dog類去被AppClassLoader(系統類載入器去載入)。然後我們在Person類中去訪問Dog類。看看能否訪問成功。 **測試環境:把我們的Person.class放置在C:\Program Files\Java\jdk1.8.0_131\jre\classes這個目錄下,那麼我們的Person.class就會被我們的啟動類載入器載入,而我們的Dog類是被AppClassLoader進行載入,我們的Person類 中引用我們的Dog類會丟擲異常.** 建立main方法進行測試: ```java package com.test; import java.lang.reflect.Method; /** * jvm 類載入器 第一章 * @author 奇客時間-時光 * 自定義類載入器——名稱空間 * 測試父載入所載入的類,不能訪問子載入器所載入的類。 */ public class MainClass02 { public static void main(String[] args) throws Exception { System.out.println("Person的類載入器:"+Person.class.getClassLoader()); System.out.println("Dog的類載入器:"+Dog.class.getClassLoader()); Class clazz = Person.class; clazz.newInstance(); } } 執行結果: "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=59226:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass02 Person的類載入器:null Dog的類載入器:sun.misc.Launcher$AppClassLoader@18b4aac2 person類被初始化了 Exception in thread "main" java.lang.NoClassDefFoundError: com/test/Dog at com.test