深入探究JVM之類加載與雙親委派機(jī)制
閱讀文本大概需要3分鐘。
前言
前面學(xué)習(xí)了虛擬機(jī)的內(nèi)存結(jié)構(gòu)、對(duì)象的分配和創(chuàng)建,但對(duì)象所對(duì)應(yīng)的類是怎么加載到虛擬機(jī)中來(lái)的呢?加載過(guò)程中需要做些什么?什么是雙親委派機(jī)制以及為什么要打破雙親委派機(jī)制?
類的生命周期

類的生命周期包含了如上的7個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析統(tǒng)稱為連接?,類的加載主要是前五個(gè)階段,每個(gè)階段基本上保持如上順序開(kāi)始(僅僅是開(kāi)始,實(shí)際上執(zhí)行是交叉混合的),只有解析階段不一定,在初始化后也有可能才開(kāi)始執(zhí)行解析,這是為了支持動(dòng)態(tài)語(yǔ)言。
加載
加載就是將字節(jié)碼的二進(jìn)制流轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),并生成類所對(duì)象的Class對(duì)象,字節(jié)碼二進(jìn)制流可以是我們編譯后的class文件,也可以從網(wǎng)絡(luò)中獲取,或者運(yùn)行時(shí)動(dòng)態(tài)生成(動(dòng)態(tài)代理)等等。
那什么時(shí)候會(huì)觸發(fā)類加載呢?這個(gè)在虛擬機(jī)規(guī)范中沒(méi)有明確定義,只是規(guī)定了何時(shí)需要執(zhí)行初始化(稍后詳細(xì)分析)。
驗(yàn)證
這個(gè)階段很好理解,就是進(jìn)行必要的校驗(yàn),確保加載到內(nèi)存中的字節(jié)碼是符合要求的,主要包含以下四個(gè)校驗(yàn)步驟(了解即可):
文件格式校驗(yàn):這個(gè)階段要校驗(yàn)的東西非常多,主要的有下面這些(實(shí)際上遠(yuǎn)遠(yuǎn)不止)
是否以魔數(shù)0xCAFEBABE開(kāi)頭。
主、次版本號(hào)是否在當(dāng)前Java虛擬機(jī)接受范圍之內(nèi)。
常量池的常量中是否有不被支持的常量類型(檢查常量tag標(biāo)志)。
指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量。
CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的數(shù)據(jù)。
Class文件中各個(gè)部分及文件本身是否有被刪除的或附加的其他信息。
元數(shù)據(jù)校驗(yàn):對(duì)字節(jié)碼描述信息進(jìn)行語(yǔ)義分析。
這個(gè)類是否有父類(除了java.lang.Object之外,所有的類都應(yīng)當(dāng)有父類)。
這個(gè)類的父類是否繼承了不允許被繼承的類(被final修飾的類)。
如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法。
類中的字段、方法是否與父類產(chǎn)生矛盾(例如覆蓋了父類的final字段,或者出現(xiàn)不符合規(guī)則的方法重載,例如方法參數(shù)都一致,但返回值類型卻不同等)。
字節(jié)碼校驗(yàn):確保程序沒(méi)有語(yǔ)法和邏輯錯(cuò)誤,這是整個(gè)驗(yàn)證階段最復(fù)雜的一個(gè)步驟。
保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作,例如不會(huì)出現(xiàn)類似于“在操作棧放置了一個(gè) int 類型的數(shù)據(jù),使用時(shí)卻按 long 類型來(lái)加載入本地變量表中”這樣的情況。
保證任何跳轉(zhuǎn)指令都不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上。
保證方法體中的類型轉(zhuǎn)換總是有效的,例如可以把-個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型,這是安全的,但是把父類對(duì)象賦值給子類數(shù)據(jù)類型,甚至把對(duì)象賦值給與它毫無(wú)繼承關(guān)系、完全不相干的一個(gè)數(shù)據(jù)類型,則是危險(xiǎn)和不合法的。
符號(hào)引用驗(yàn)證:這個(gè)階段發(fā)生在符號(hào)引用轉(zhuǎn)為直接引用的時(shí)候,即實(shí)際上是在解析階段中進(jìn)行的。
符號(hào)引用中通過(guò)字符串描述的全限定名是否能找到對(duì)應(yīng)的類。
在指定類中是否存在符合方法的字段描述符及簡(jiǎn)單名稱所描述的方法和字段。
符號(hào)引用中的類、字段、方法的可訪問(wèn)性( private、 protected、public
?是否可被當(dāng)前類訪問(wèn)。
準(zhǔn)備
該階段是為類變量(static)分配內(nèi)存并設(shè)置零值,即類只要經(jīng)過(guò)準(zhǔn)備階段其中的靜態(tài)變量就是可使用的了,但此時(shí)類變量的值還不是我們想要的值,需要經(jīng)過(guò)初始化階段才會(huì)將我們希望的值賦值給對(duì)應(yīng)的靜態(tài)變量。
解析
解析就是將常量池中的符號(hào)引用替換為直接引用的過(guò)程。符號(hào)引用就是一個(gè)代號(hào),比如我們的名字,而這里可以理解為就是類的完全限定名;直接引用則是對(duì)應(yīng)的具體的人、物,這里就是指目標(biāo)的內(nèi)存地址。為什么需要符號(hào)引用呢?因?yàn)轭愒诩虞d到內(nèi)存之前還沒(méi)有分配內(nèi)存地址,因此必然需要一個(gè)東西指代它。這個(gè)階段包含了類或接口的解析、字段解析、類方法解析、接口方法解析,在解析的過(guò)程中可能會(huì)拋出以下異常:
java.lang.NoSuchFieldError:找不到字段
java.lang.IllegalAccessError:不具有訪問(wèn)權(quán)限
java.lang.NoSuchMethodError:找不到方法
初始化
這是類加載過(guò)程中的最后一個(gè)步驟,主要是收集類的靜態(tài)變量的賦值動(dòng)作和static塊中的語(yǔ)句合成
遇到new、getstatic、putstatic或invokestatic這四條字節(jié)碼指令時(shí)。能夠生成這四條指令的典型Java代碼場(chǎng)景有:
使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候。
讀取或設(shè)置一個(gè)類型的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候。
調(diào)用一個(gè)類型的靜態(tài)方法的時(shí)候。
反射調(diào)用類時(shí)。
當(dāng)初始化類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類的初始化。
當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
當(dāng)使用JDK 7新加入的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
當(dāng)一個(gè)接口中定義了JDK 8新加入的默認(rèn)方法(被default關(guān)鍵字修飾的接口方法)時(shí),如果有這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化,那該接口要在其之前被初始化。
下面分析幾個(gè)案例代碼,讀者們可以先思考后再運(yùn)行代碼看看和自己想的是否一樣。
案例一
先定義如下兩個(gè)類:
public?class?SuperClazz?{
????static??{
????????System.out.println("SuperClass init!");
????}
????public?static?int?value=123;
????public?static?final?String?HELLOWORLD="hello?world";
????public?static?final?int?WHAT?=?value;
}
public?class?SubClaszz?extends?SuperClazz?{
????static{
????????System.out.println("SubClass init!");
????}
}
然后進(jìn)行下面的調(diào)用:
public?class?Initialization?{
????public?static?void?main(String[]args){
????????Initialization?initialization?=?new?Initialization();
????????initialization.M1();
????}
????public?void?M1(){
????????System.out.println(SubClaszz.value);
????}
}
第一個(gè)案例是通過(guò)子類去引用父類中的靜態(tài)變量,兩個(gè)類都會(huì)加載和初始化么?打印結(jié)果看看:
SuperClass?init!
123
可以看到只有父類初始化了,那么父類必然是加載了的,問(wèn)題就在于子類有沒(méi)有被加載呢?可以加上參數(shù):-XX:+TraceClassLoading再執(zhí)行(該參數(shù)的作用就是打印被加載了的類),可以看到子類是被加載了的。所以通過(guò)子類引用父類靜態(tài)變量,父子類都會(huì)被加載,但只有父類會(huì)進(jìn)行初始化。
為什么呢?反編譯后可以看到生成了如下指令:
0:?getstatic?????#5??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
3:?getstatic?????#6??????????????????//?Field?ex7/init/SubClaszz.value:I
6:?invokevirtual?#7??????????????????//?Method?java/io/PrintStream.println:(I)V
9:?return
關(guān)鍵就是getstatic指令就會(huì)觸發(fā)類的初始化,但是為什么子類不會(huì)初始化呢?因?yàn)檫@個(gè)變量是來(lái)自于父類的,為了提高效率,所以虛擬機(jī)進(jìn)行了優(yōu)化,這種情況只需要初始化父類就行了。
案例二
調(diào)用下面的方法:
public?void?M2(){
?????SubClaszz[]sca?=?new?SubClaszz[10];
}執(zhí)行后可以發(fā)現(xiàn),使用數(shù)組,不會(huì)觸發(fā)初始化,但父子類都會(huì)被加載。
案例三
public?void?M3(){
????System.out.println(SuperClazz.HELLOWORLD);
}
引用常量不會(huì)觸發(fā)類的加載和初始化,因?yàn)槌A吭诰幾g后就已經(jīng)存在當(dāng)前class的常量池。
案例四
public?void?M4(){
??????System.out.println(SubClaszz.WHAT);
}
通過(guò)常量去引用其它的靜態(tài)變量會(huì)發(fā)生什么呢?這個(gè)和案例一結(jié)果是一樣的。
類加載器
類加載器和雙親委派模型
在我們平時(shí)開(kāi)發(fā)中,確定一個(gè)類需要通過(guò)完全限定名,而不能簡(jiǎn)單的通過(guò)名字,因?yàn)樵诓煌穆窂较挛覀兪强梢远x同名的類的。那么在虛擬機(jī)中又是怎么區(qū)分類的呢?在虛擬機(jī)中需要類加載器+完全限定名一起來(lái)指定一個(gè)類的唯一性,即相同限定名的類若由兩個(gè)不同的類加載器加載,那虛擬機(jī)就不會(huì)把它們當(dāng)做一個(gè)類。從這里我們可以看出類加載器一定是有多個(gè)的,那么不同的類加載器是怎么組織的?它們又分別需要加載哪些類呢?
從虛擬角度看,只有兩種類型的類加載器:啟動(dòng)類加載器(BootstrapClassLoader)和非啟動(dòng)類加載器。前者是C++實(shí)現(xiàn),屬于虛擬機(jī)的一部分,后者則是由Java實(shí)現(xiàn)的,獨(dú)立于虛擬機(jī)的外部,并且全部繼承自抽象類java.lang.ClassLoader。
但從Java本身來(lái)看,一直保持著三層類加載器、雙親委派的結(jié)構(gòu),當(dāng)然除了Java本身提供的三層類加載器,我們還可以自定義實(shí)現(xiàn)類加載器。如上圖,上面三個(gè)就是原生的類加載器,每一個(gè)都是下一個(gè)類加載器的父加載器,注意這里都是采用組合而非繼承。當(dāng)開(kāi)始加載類時(shí),首先交給父加載器加載,父加載器加載了子加載器就不用再加載了,而若是父加載器加載不了,就會(huì)交給子加載器加載,這就是雙親委派機(jī)制。這就好比工作中遇到了無(wú)法處理的事,你會(huì)去請(qǐng)示直接領(lǐng)導(dǎo),直接領(lǐng)導(dǎo)處理不了,再找上層領(lǐng)導(dǎo),然后上層領(lǐng)導(dǎo)覺(jué)得這是個(gè)小事,不用他親自動(dòng)手,就讓你的直接領(lǐng)導(dǎo)去做,接著他又交給你去做等等。下面來(lái)看看每個(gè)類加載器的具體作用:
BootstrapClassLoader:?jiǎn)?dòng)類加載器,顧名思義,這個(gè)類加載器主要負(fù)責(zé)加載JDK lib包,以及-Xbootclasspath參數(shù)指定的目錄,并且虛擬機(jī)對(duì)文件名進(jìn)行了限定,也就是說(shuō)即使我們自己寫個(gè)jar放入到上述目錄,也不會(huì)被加載。由于該類加載器是C++使用,所以我們的Java程序中無(wú)法直接引用,調(diào)用java.lang.ClassLoader.getClassLoader()方法時(shí)默認(rèn)返回的是null。
ExtClassLoader:擴(kuò)展類加載器,主要負(fù)責(zé)加載JDK lib/ext包,以及被系統(tǒng)變量java.ext.dirs指向的所有類庫(kù),這個(gè)類庫(kù)可以存放我們自己寫的通用jar。
AppClassLoader:應(yīng)用程序類加載器,負(fù)責(zé)加載用戶classpath上的所有類。它是java.lang.ClassLoader.getSystemClassLoader()的返回值,也是我們程序的默認(rèn)類加載器(如果我們沒(méi)有自定義類加載器的話)。
通過(guò)這三個(gè)類加載以及雙親委派機(jī)制,一個(gè)顯而易見(jiàn)的好處就是,不同的類隨它的類加載器天然具有了加載優(yōu)先級(jí),像Object、String等等這些核心類庫(kù)自然就會(huì)在我們的應(yīng)用程序類之前被加載,使得程序更安全,不會(huì)出現(xiàn)錯(cuò)誤,Spring的父子容器也是這樣的一個(gè)設(shè)計(jì)。通過(guò)下面這段代碼可以看到每個(gè)類所對(duì)應(yīng)的類加載器:
public?class?ClassLoader?{
????public?static?void?main(String[]?args)?{
????????System.out.println(String.class.getClassLoader());?//啟動(dòng)類加載器
????????System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());//拓展類加載器
????????System.out.println(ClassLoader.class.getClassLoader());//應(yīng)用程序類加載器
????}
}
輸出:
null
sun.misc.Launcher$ExtClassLoader@4b67cf4d
sun.misc.Launcher$AppClassLoader@14dad5dc
破壞雙親委派模型
剛剛我舉了工作中的一個(gè)例子來(lái)說(shuō)明雙親委派機(jī)制,但現(xiàn)實(shí)中我們不需要事事都去請(qǐng)示領(lǐng)導(dǎo),同樣類加載器也不是完全遵循雙親委派機(jī)制,在必要的時(shí)候是可以打破這個(gè)規(guī)則的。下面列舉四個(gè)破壞的情況,在此之前我們需要先了解下雙親 委派的代碼實(shí)現(xiàn)原理,在java.lang.ClassLoader類中有一個(gè)loadClass以及findClass方法:
?protected?Class>?loadClass(String?name,?boolean?resolve)
????????throws?ClassNotFoundException
????{
????????synchronized?(getClassLoadingLock(name))?{
????????????//?First,?check?if?the?class?has?already?been?loaded
????????????Class>?c?=?findLoadedClass(name);
????????????if?(c?==?null)?{
????????????????long?t0?=?System.nanoTime();
????????????????try?{
????????????????????if?(parent?!=?null)?{
????????????????????????c?=?parent.loadClass(name,?false);
????????????????????}?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();
????????????????}
????????????}
????????????if?(resolve)?{
????????????????resolveClass(c);
????????????}
????????????return?c;
????????}
????}
????protected?Class>?findClass(String?name)?throws?ClassNotFoundException?{
????????throw?new?ClassNotFoundException(name);
????}
從上面可以看到首先是調(diào)用parent去加載類,沒(méi)有加載到才調(diào)用自身的findClass方法去加載。也就是說(shuō)用戶在實(shí)現(xiàn)自定義類加載器的時(shí)候需要覆蓋的是fiindClass而不是loadClass,這樣才能滿足雙親委派模型。
下面具體來(lái)看看破壞雙親委派的幾個(gè)場(chǎng)景。
第一次
第一次破壞是在雙親委派模型出現(xiàn)之前, 因?yàn)樵撃P褪窃贘DK1.2之后才引入的,那么在此之前,抽象類java.lang.ClassLoader就已經(jīng)存在了,用戶自定義的類加載器都會(huì)去覆蓋該類中的loadClass方法,所以雙親委派模型出現(xiàn)后,就無(wú)法避免用戶覆蓋該方法,因此新增了findClass引導(dǎo)用戶去覆蓋該方法實(shí)現(xiàn)自己的類加載邏輯。
SPI
第二次破壞是由于這個(gè)模型本身缺陷導(dǎo)致的,因?yàn)樵撃P捅WC了類的加載優(yōu)先級(jí),但是有些接口是Java定義在核心類庫(kù)中,但具體的服務(wù)實(shí)現(xiàn)是由用戶提供的,這時(shí)候就不得不破壞該模型才能實(shí)現(xiàn),典型的就是Java中的SPI機(jī)制(對(duì)SPI不了解的讀者可以翻閱我之前的文章或是其它資料,這里不進(jìn)行闡述)。J
DBC的驅(qū)動(dòng)加載就是SPI實(shí)現(xiàn)的,所以直接看到java.sql.DriverManager類,該類中有一個(gè)靜態(tài)初始化塊:
static?{
????????loadInitialDrivers();
????????println("JDBC?DriverManager?initialized");
????}
????private?static?void?loadInitialDrivers()?{
????????String?drivers;
????????try?{
????????????drivers?=?AccessController.doPrivileged(new?PrivilegedAction()?{
????????????????public?String?run()?{
????????????????????return?System.getProperty("jdbc.drivers");
????????????????}
????????????});
????????}?catch?(Exception?ex)?{
????????????drivers?=?null;
????????}
????????AccessController.doPrivileged(new?PrivilegedAction()?{
????????????public?Void?run()?{
????????????????ServiceLoader?loadedDrivers?=?ServiceLoader.load(Driver.class);
????????????????Iterator?driversIterator?=?loadedDrivers.iterator();
????????????????try{
????????????????????while(driversIterator.hasNext())?{
????????????????????????driversIterator.next();
????????????????????}
????????????????}?catch(Throwable?t)?{
????????????????//?Do?nothing
????????????????}
????????????????return?null;
????????????}
????????});
????????println("DriverManager.initialize:?jdbc.drivers?=?"?+?drivers);
????????if?(drivers?==?null?||?drivers.equals(""))?{
????????????return;
????????}
????????String[]?driversList?=?drivers.split(":");
????????println("number?of?Drivers:"?+?driversList.length);
????????for?(String?aDriver?:?driversList)?{
????????????try?{
????????????????println("DriverManager.Initialize:?loading?"?+?aDriver);
????????????????Class.forName(aDriver,?true,
????????????????????????ClassLoader.getSystemClassLoader());
????????????}?catch?(Exception?ex)?{
????????????????println("DriverManager.Initialize:?load?failed:?"?+?ex);
????????????}
????????}
????}
主要看ServiceLoader.load方法,這個(gè)就是通過(guò)SPI去加載我們引入java.sql.Driver實(shí)現(xiàn)類(比如引入mysql的驅(qū)動(dòng)包就是com.mysql.cj.jdbc.Driver):
public?static??ServiceLoader?load(Class<S>?service)?{
????????ClassLoader?cl?=?Thread.currentThread().getContextClassLoader();
????????return?ServiceLoader.load(service,?cl);
????}
這個(gè)方法主要是從當(dāng)前線程中獲取類加載器,然后通過(guò)這個(gè)類加載器去加載驅(qū)動(dòng)實(shí)現(xiàn)類(這個(gè)叫線程上下文類加載器,我們也可以使用這個(gè)技巧去打破雙親委派),那這里會(huì)獲取到哪一個(gè)類加載器呢?具體的設(shè)置是在sun.misc.Launcher類的構(gòu)造器中:
public?Launcher()?{
????????Launcher.ExtClassLoader?var1;
????????try?{
????????????var1?=?Launcher.ExtClassLoader.getExtClassLoader();
????????}?catch?(IOException?var10)?{
????????????throw?new?InternalError("Could?not?create?extension?class?loader",?var10);
????????}
????????try?{
????????????this.loader?=?Launcher.AppClassLoader.getAppClassLoader(var1);
????????}?catch?(IOException?var9)?{
????????????throw?new?InternalError("Could?not?create?application?class?loader",?var9);
????????}
????????Thread.currentThread().setContextClassLoader(this.loader);
????????String?var2?=?System.getProperty("java.security.manager");
????????if?(var2?!=?null)?{
????????????SecurityManager?var3?=?null;
????????????if?(!"".equals(var2)?&&?!"default".equals(var2))?{
????????????????try?{
????????????????????var3?=?(SecurityManager)this.loader.loadClass(var2).newInstance();
????????????????}?catch?(IllegalAccessException?var5)?{
????????????????}?catch?(InstantiationException?var6)?{
????????????????}?catch?(ClassNotFoundException?var7)?{
????????????????}?catch?(ClassCastException?var8)?{
????????????????}
????????????}?else?{
????????????????var3?=?new?SecurityManager();
????????????}
????????????if?(var3?==?null)?{
????????????????throw?new?InternalError("Could?not?create?SecurityManager:?"?+?var2);
????????????}
????????????System.setSecurityManager(var3);
????????}
????}
可以看到設(shè)置的就是AppClassLoader。你可能會(huì)有點(diǎn)疑惑,這個(gè)類加載器加載類的時(shí)候不也是先調(diào)用父類加載器加載么,怎么就打破雙親委派了呢?其實(shí)打破雙親委派指的就是類的層次結(jié)構(gòu),延伸意思就是類的加載優(yōu)先級(jí),這里本應(yīng)該是在加載核心類庫(kù)的時(shí)候卻提前將我們應(yīng)用程序中的類庫(kù)給加載到虛擬機(jī)中來(lái)了。
Tomcat

上圖是Tomcat類加載的類圖,前面三個(gè)不用說(shuō),CommonClassLoader、CatalinaClassLoader、SharedClassLoader、WebAppClassLoader、JspClassLoader則是Tomcat自己實(shí)現(xiàn)的類加載器,分別加載common包、server包、shared包、WebApp/WEB-INF/lib包以及JSP文件,前面三個(gè)在tomcat 6之后已經(jīng)合并到根目錄下的lib目錄下。而WebAppClassLoader則是每一個(gè)應(yīng)用程序?qū)?yīng)一個(gè),JspClassLoader是每一個(gè)JSP文件都會(huì)對(duì)應(yīng)一個(gè),并且這兩個(gè)類加載器都沒(méi)有父類加載器,這也就違背了雙親委派模型。
為什么每個(gè)應(yīng)用程序需要單獨(dú)的WebAppClassLoader實(shí)例?因?yàn)槊總€(gè)應(yīng)用程序需要彼此隔離,假如在兩個(gè)應(yīng)用中定義了一樣的類(完全限定名),如果遵循雙親委派那就只會(huì)存在一份了,另外不同的應(yīng)用還有可能依賴同一個(gè)類庫(kù)的不同版本,這也需要隔離,所以每一個(gè)應(yīng)用程序都會(huì)對(duì)應(yīng)一個(gè)WebAppClassLoader,它們共享的類庫(kù)可以讓SharedClassLoader加載,另外這些類加載加載的類對(duì)Tomcat本身來(lái)說(shuō)也是隔離的(CatalinaClassLoader加載的)。
為什么每個(gè)JSP文件需要對(duì)應(yīng)單獨(dú)的一個(gè)JspClassLoader實(shí)例?這是由于JSP是支持運(yùn)行時(shí)修改的,修改后會(huì)丟棄掉之前編譯生成的class,并重新生成一個(gè)JspClassLoader實(shí)例去加載新的class。
以上就是Tomcat為什么要打破雙親委派模型的原因。
OSGI
OSGI是用于實(shí)現(xiàn)模塊熱部署,像Eclipse的插件系統(tǒng)就是利用OSGI實(shí)現(xiàn)的,這個(gè)技術(shù)非常復(fù)雜同時(shí)使用的也越來(lái)越少了,感興趣的讀者可自行查閱資料學(xué)習(xí),這里不再進(jìn)行闡述。
總結(jié)
類加載的過(guò)程讓我們了解到一個(gè)類是如何被加載到內(nèi)存中,需要經(jīng)過(guò)哪些階段;而類加載器和雙親委派模型則是告訴我們應(yīng)該怎么去加載類、類的加載優(yōu)先級(jí)是怎樣的,其中的設(shè)計(jì)思想我們也可以學(xué)習(xí)借鑒;最后需要深刻理解的是為什么需要打破雙親委派,在遇到相應(yīng)的場(chǎng)景時(shí)應(yīng)該怎么做。
source:?https://www.cnblogs.com/yewy/p/13414144.html☆
往期精彩
☆
01?Sentinel如何進(jìn)行流量監(jiān)控
02?Nacos源碼編譯
03?基于Apache Curator框架的ZooKeeper使用詳解
關(guān)注我
每天進(jìn)步一點(diǎn)點(diǎn)
