淺談java類加載過程和雙親委派機制

? ? ? 首先我們需要明確,java中的類只會加載一次,而且java中的類是一種惰性加載,姬用到哪個類,才會去加載哪和個類這個其實很好理解,我們在實際開發(fā)項目中引入了很多包,這里面的每個類不一定都能用到,jvm啟動運行的時候不可能一次把所有的類都加載一遍。一定是程序運行過程中需要哪個類去加載哪個類。

那么那些場景會導(dǎo)致類的主動加載呢?

1、\color{red}{通過new關(guān)鍵字導(dǎo)致類的初始化},這是最常見的一種類的加載方式,當(dāng)我們創(chuàng)建一個對象的時候,首都需要看一下這個類是否加載,如果沒有加載類,需要先加載類。2、訪問類的靜態(tài)變量,2、訪問類的靜態(tài)變量\color{red}{注意這里是靜態(tài)變量而不是靜態(tài)常量。訪問類的靜態(tài)常量不會導(dǎo)致類的初始化}

3、訪問類的靜態(tài)方法

4、對某個類進(jìn)行反射操作

5、初始化子類會導(dǎo)致父類的初始化

6、啟動類,執(zhí)行main函數(shù)所在類導(dǎo)致類的初始化

? ?這里需要注意的是 構(gòu)造一個類的數(shù)組不會導(dǎo)致類的加載,僅僅是分配了一定的內(nèi)存空間 比如 A[]a=new A[5];這里不會有A的類加載,另外 A a=null,這種情況也不會有類的加載。

了解了類的加載的觸發(fā)過程,那我們需要探究一下類的加載過程,類的加載過程可以分為一下幾步

1、加載? 從磁盤通過IO讀取claaa字節(jié)碼文件,往jvm虛擬機加載

2、驗證?校驗字節(jié)碼文件的正確性,是否符合ava規(guī)范

3、準(zhǔn)備?給類的靜態(tài)變量分配內(nèi)存,并且分配默認(rèn)值,int->0,boolean->false,引用類型->null等

4、解析?將符號引用轉(zhuǎn)變?yōu)橹苯右茫o態(tài)鏈接過程)將靜態(tài)方法(方法名,變量名)轉(zhuǎn)化為內(nèi)存地址。靜態(tài)方法的符號對應(yīng)地址在類加載期間就可以確定,并且不會改變,所以可以做靜態(tài)鏈接

5、初始化?對類的靜態(tài)變量初始化賦真正的值?執(zhí)行靜態(tài)代碼塊(這里要多說一次,靜態(tài)代碼塊就是這個時候執(zhí)行的,因為類只加載一次,所以靜態(tài)代碼塊也只執(zhí)行一次,小伙伴們面試經(jīng)常碰到面試題,看一段代碼,判斷執(zhí)行結(jié)果,而這段代碼往往就有靜態(tài)代碼塊,其實考察的就是類加載故叢橫)

我們知道java是一門面向?qū)ο笳Z言,,前面我們了解了類的加載過程,那么對于java來說,誰來完成我們的類的加載這個工作呢,這就是我們下面要說的,類加載器,類加載器其實沒有什么神秘的,它就是一個對象,干的就是類加載的活。嚴(yán)格來說,類加載器分為:

1、引導(dǎo)類加載器(bootstrapLoader)負(fù)責(zé)加載支撐JVM運行的位于JRE的lib目錄下的核心類庫,比如?rt.jar、charsets.jar等

2、擴展類加載器(ExtClassLoader)負(fù)責(zé)加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR?類包

3、應(yīng)用程序類加載器(AppClassLoader)負(fù)責(zé)加載ClassPath路徑下的類包,主要就是加載你自己寫的那?些類

4、自定義加載器 開發(fā)者自己寫的類加載器,可以指定加載路徑

下面這個代碼輸出結(jié)果可以很好的體現(xiàn)這幾種類加載器

public class Demo1 {

? ? public static void main(String[] args) {

? ? ? ? ?System.out.println(String.class.getClassLoader());

? ? ? ? ?System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());

? ? ? ? System.out.println(Demo1.class.getClassLoader().getClass().getName());

? ? ? }

}

運行結(jié)果

null?

sun.misc.Launcher$ExtClassLoader

sun.misc.Launcher$AppClassLoader

\color{red}{需要說明的是 String類的加載器是引導(dǎo)類加載器,這個類是c++寫的,在jvm中看不到,所以是null}

我們知道了類的加載過程,加載類的雙親委派機制,那么什么是雙親委派機制呢,其實雙親委派機制可以這么描述,加載某個類時會先委托父加載器尋找目標(biāo)類,找不到再委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標(biāo)類,則在自己的類加載路徑中查找并載入目標(biāo)類。概括起來一句化,父加載器先加載,加載不到自己才去加載。下面的圖會更詳細(xì)體現(xiàn)這個過程



這里我們需要明確,類加載器的父類加載器并不是繼承關(guān)系,比如應(yīng)用程序類加載器的父加載器是擴展類加載器,但是二者并不是繼承關(guān)系,而是應(yīng)用程序類加載器的字段成員屬性parent是引導(dǎo)類加載器。下面這個圖展示的是從c++創(chuàng)建引導(dǎo)類加載器到初始化擴展類加載器和應(yīng)用程序類加載器的過程,從Launcher類的源碼可以看出,初始化先初始化了擴展類加載器,然后作為參數(shù)傳入初始化應(yīng)用程序類加載器當(dāng)中


下面我們看一下實現(xiàn)雙親委派機制的源碼,所有的類加載器都是繼承ClassLoader類,該類的loadClass方法就是主要實現(xiàn)了雙親委派機制

protected Class<?> loadClass(String name, boolean resolve)

? ? ? ? throws ClassNotFoundException

? ? {

? ? ? ? synchronized (getClassLoadingLock(name)) {

? ? ? ? ? ? // 先檢查類是不是被加載過,保證類只加載一次

? ? ? ? ? ? Class<?> c = findLoadedClass(name);

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

? ? ? ? ? ? ? ? long t0 = System.nanoTime();

? ? ? ? ? ? ? ? try {

? ? ? ? // 如果parent存在,由parent加載

? ? ? ? ? ? ? ? ? ? if (parent != null) {

? ? ? ? ? ? ? ? ? ? ? ? c = parent.loadClass(name, false);

? ? ? // 擴展類加載器的parent為null 用引導(dǎo)類加載器加載

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? c = findBootstrapClassOrNull(name);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } catch (ClassNotFoundException e) {

? ? ? ? ? ? ? ? ? ? // ClassNotFoundException thrown if class not found

? ? ? ? ? ? ? ? ? ? // from the non-null parent class loader

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? //父類沒有加載到,自己加載

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

? ? ? ? ? ? ? ? ? ? // If still not found, then invoke findClass in order

? ? ? ? ? ? ? ? ? ? // to find the class.

? ? ? ? ? ? ? ? ? ? long t1 = System.nanoTime();

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

? ? ? ? ? ? ? ? ? ? // this is the defining class loader; record the stats

? ? ? ? ? ? ? ? ? ? sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

? ? ? ? ? ? ? ? ? ? sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

? ? ? ? ? ? ? ? ? ? sun.misc.PerfCounter.getFindClasses().increment();

? ? ? ? ? ? ? ? }

下面我們來探討一下,為什么java要設(shè)計雙親委派機制:

1、避免類的重復(fù)加載:當(dāng)父親已經(jīng)加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性

2、沙箱安全機制,防止核心API庫被隨意篡改,這個這么理解呢,我們知道類的加載是根據(jù)全限定包名類名來確定的,也就是說我全限定包名類名相同,jvm便認(rèn)為是一個類,那么如果我們自己寫一個String類,全限定包名類名和jdk源碼的包名類名一樣,那是不是我們就可以篡改jdkString類的api了呢,通過雙親委派機制,盡管我們可已寫這么一個類,但是加載類的時候,應(yīng)用程序類加載器會先讓父加載器擴展類加載器加載,而擴展類加載器會先讓引導(dǎo)類加載器加載,引導(dǎo)類加載器加載的是jdk自己的String類,這就導(dǎo)致我們的string類不加載,這就保證了jdk的核心api不會被篡改。

那么是否可以打破雙親委派機制呢

? 我們可以通過自定義類加載器拉打破雙親委派機制。核心的是我們繼承Classloader這個類,重寫loadClass方法便可以實現(xiàn)按照自己意愿去加載類而不用遵循雙親委派機制Tomcat就是個很好的例子,TomCat為什么要打破雙親委派機制

我們思考一下:Tomcat是個web容器, 那么它要解決什么問題:

1. 一個web容器可能需要部署兩個應(yīng)用程序,不同的應(yīng)用程序可能會依賴同一個第三方類庫的 不同版本,不能要求同一個類庫在同一個服務(wù)器只有一份,因此要保證每個應(yīng)用程序的類庫都是 獨立的,保證相互隔離。

2. 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果服務(wù)器有10個應(yīng)用程 序,那么要有10份相同的類庫加載進(jìn)虛擬機。

3. web容器也有自己依賴的類庫,不能與應(yīng)用程序的類庫混淆?;诎踩紤],應(yīng)該讓容器的 類庫和程序的類庫隔離開來。

4. web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中 運行,但程序運行后修改jsp已經(jīng)是司空見慣的事情, web容器需要支持 jsp 修改后不用重啟。

再看看我們的問題:Tomcat 如果使用默認(rèn)的雙親委派類加載機制行不行? 答案是不行的。為什么?

?第一個問題,如果使用默認(rèn)的類加載器機制,那么是無法加載兩個相同類庫的不同版本的,默認(rèn) 的類加器是不管你是什么版本的,只在乎你的全限定類名,并且只有一份。 第二個問題,默認(rèn)的類加載器是能夠?qū)崿F(xiàn)的,因為他的職責(zé)就是保證唯一性。 第三個問題和第一個問題一樣。 我們再看第四個問題,我們想我們要怎么實現(xiàn)jsp文件的熱加載,jsp 文件其實也就是class文 件,那么如果修改了,但類名還是一樣,類加載器會直接取方法區(qū)中已經(jīng)存在的,修改后的jsp 是不會重新加載的。那么怎么辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以你應(yīng)該想 到了,每個jsp文件對應(yīng)一個唯一的類加載器,當(dāng)一個jsp文件修改了,就直接卸載這個jsp類加載 器。重新創(chuàng)建類加載器,重新加載jsp文件




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

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

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