java虛擬機只會在不同的類的類名相同且加載該類的加載器均相同的情況下才會判定這是一個類。如果沒有雙親委派機制,同一個類可能就會被多個類加載器加載,如此類就可能會被識別為兩個不同的類,相互賦值時問題就會出現。
雙親委派機制能夠保證多加載器加載某個類時,最終都是由一個加載器加載,確保最終加載結果相同。
沒有雙親委派模型,讓所有類加載器自行加載的話,假如用戶自己編寫了一個稱為java.lang.Object的類,並放在程序的ClassPath中,系統就會出現多個不同的Object類, Java類型體系中基礎行為就無法保證,應用程序就會變得一片混亂。
在介紹雙親委派機制的時候,不得不提ClassLoader(類加載器)。說ClassLoader之前,我們得先了解下Java的基本知識。
Java是運行在Java的虛擬機(JVM)中的,但是它是如何運行在JVM中了呢?我們在IDE中編寫的Java源代碼被編譯器編譯成.class的字節碼文件。然后由我們得ClassLoader負責將這些class文件給加載到JVM中去執行。
JVM中提供了三層的ClassLoader:
Bootstrap classLoader:主要負責加載核心的類庫(java.lang.*等),構造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要負責加載jre/lib/ext目錄下的一些擴展的jar。
AppClassLoader:主要負責加載應用程序的主函數類
那如果有一個我們寫的Hello.java編譯成的Hello.class文件,它是如何被加載到JVM中的呢?別着急,請繼續往下看。
打開“java.lang”包下的ClassLoader類。然后將代碼翻到loadClass方法:
1 public Class<?> loadClass(String name) throws ClassNotFoundException { 2 return loadClass(name, false); 3 } 4 // -----??----- 5 protected Class<?> loadClass(String name, boolean resolve) 6 throws ClassNotFoundException 7 { 8 // 首先,檢查是否已經被類加載器加載過 9 Class<?> c = findLoadedClass(name); 10 if (c == null) { 11 try { 12 // 存在父加載器,遞歸的交由父加載器 13 if (parent != null) { 14 c = parent.loadClass(name, false); 15 } else { 16 // 直到最上面的Bootstrap類加載器 17 c = findBootstrapClassOrNull(name); 18 } 19 } catch (ClassNotFoundException e) { 20 // ClassNotFoundException thrown if class not found 21 // from the non-null parent class loader 22 } 23 24 if (c == null) { 25 // If still not found, then invoke findClass in order 26 // to find the class. 27 c = findClass(name); 28 } 29 } 30 return c; 31 }
當獲取parent是null時,代表獲取到的時頂級類加載器,Bootstrap類加載器。
其實這段代碼已經很好的解釋了雙親委派機制,為了大家更容易理解,我做了一張圖來描述一下上面這段代碼的流程:
從上圖中我們就更容易理解了,當一個Hello.class這樣的文件要被加載時。不考慮我們自定義類加載器,首先會在AppClassLoader中檢查是否加載過,如果有那就無需再加載了。如果沒有,那么會拿到父加載器,然后調用父加載器的loadClass方法。父類中同理也會先檢查自己是否已經加載過,如果沒有再往上。注意這個類似遞歸的過程,直到到達Bootstrap classLoader之前,都是在檢查是否加載過,並不會選擇自己去加載。直到BootstrapClassLoader,已經沒有父加載器了,這時候開始考慮自己是否能加載了,如果自己無法加載,會下沉到子加載器去加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException。那么有人就有下面這種疑問了?
為什么要設計這種機制
這種設計有個好處是,如果有人想替換系統級別的類:String.java。篡改它的實現,在這種機制下這些系統的類已經被Bootstrap classLoader加載過了(為什么?因為當一個類需要加載的時候,最先去嘗試加載的就是BootstrapClassLoader),所以其他類加載器並沒有機會再去加載,從一定程度上防止了危險代碼的植入。
雙親委派機制,其實指的是由子到父,再由父到子的過程。主要解決的是類加載的安全問題(比如java.lang.String,如果沒有雙親委派,直接由自定義ClassLoader加載了,有了雙親委派會去父加載器,看是否可以加載)
具體為什么Bootstrap、Extension、App是加載他們自己的類,可以看sun.misc.Lancher,是他們的啟動類。相關配置決定了他們所加載的類的路徑:
類裝載方式
a隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。
b顯式裝載, 通過class.forname()等方法,顯式加載需要的類
類加載的動態性體現
java應用程序一般是由n個類組成,在程序啟動時,並不是一次把所有的類全部加載后再運行,它會先保證程序運行的基礎類能夠一次性加載到jvm中,等其它類用到JVM的時候再加載,這樣就節省了內存的開銷,java最早就是為了嵌入式系統而設計的,內存十分珍貴,所以這樣節省內存是必然的,這是一種可以理解的機制,而用到時再加載這也是java動態性的一種體現。
類裝載器就是尋找類的字節碼文件,並構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要經過以下步驟:
1) 裝載:查找和導入Class文件;
2) 鏈接:把類的二進制數據合並到JRE中;
(a)校驗:檢查載入Class文件數據的正確性;
(b)准備:給類的靜態變量分配存儲空間;
(c)解析:將符號引用轉成直接引用;
3) 初始化:對類的靜態變量,靜態代碼塊執行初始化操作
虛擬機將描述類的數據從class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。
例:
類java.lang.Object,它存在在rt.jar當中,不管是哪一個類加載器要加載這個類,最后都會是委派給處於模型最頂端的Bootstrap ClassLoader進行加載,所以,Object類在程序的各種類加載器環境中都是同一個類。
相反的來說,假如沒有雙親委派模型,而是由各個類加載器自行加載的話,假如,用戶編寫了一個java.lang.Object的同名類並放在ClassPath中,那么,系統當中將會出現多個不同的Object類,程序將混亂。
所以,假如開發者嘗試編寫一個與rt.jar類庫中重名的Java類,能夠正常編譯,可是卻永遠也不能夠被加載運行。
JVM懶加載
參考鏈接:https://blog.csdn.net/codeyanbao/article/details/82875064
https://blog.csdn.net/RaymondCoder/article/details/105941597
https://blog.csdn.net/liuyu973971883/article/details/107582623