前言
什么是類加載器?
類加載器有哪些闰非?
雙親委派機(jī)制是怎么樣的?
什么時(shí)候需要打破雙親委派峭范?
如何打破财松?
以上內(nèi)容網(wǎng)上有大量文章介紹一些基礎(chǔ)概念,這篇《類加載器纱控、雙親委派機(jī)制與打破(Driver辆毡、TCCL、Tomcat等)》甜害、《我竟然被“雙親委派”給虐了》有詳細(xì)介紹舶掖,本文著重從源碼來(lái)講解下我在理解過(guò)程中的疑問(wèn)
用途
先在前面說(shuō),了解這個(gè)有啥用唾那,我認(rèn)為有兩點(diǎn):
- 可以通過(guò)網(wǎng)絡(luò)访锻、數(shù)據(jù)庫(kù)、接口等多種形式進(jìn)行遠(yuǎn)程class的加載闹获,這個(gè)就很有用了期犬,你的代碼部署在別人那邊,核心class在你這兒
- 代碼加密避诽,你的代碼部署給用戶龟虎,自定義加載器來(lái)加載解密等操作
- 熱部署,運(yùn)行過(guò)程中沙庐,直接上傳class文件鲤妥,然后自定義加載佳吞,不用重啟,spring boot那個(gè)熱部署插件就使到這個(gè)
以上建議還要配合密鑰棉安,時(shí)間等綜合來(lái)考慮底扳,不然單一的還是沒(méi)法保障安全性
疑問(wèn)
1.jvm默認(rèn)的類加載器:AppClassLoader、ExtClassLoader贡耽、Bootstrap ClassLoader衷模;三者是如何在代碼里面沒(méi)有繼承關(guān)系,是如何進(jìn)行逐步委托加載的蒲赂?
首先他們?nèi)皇怯胑xtends進(jìn)行繼承操作的阱冶,是基于組合進(jìn)行的松耦合繼承,可以看ClassLoader類里面的parent屬性滥嘴,其次rt.jar中
sun.misc.Launcher
類中有兩個(gè)靜態(tài)類AppClassLoader
木蹬,ExtClassLoader
,查看Launcher的構(gòu)造函數(shù)如下:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//獲取ext類加載器若皱,點(diǎn)進(jìn)getExtClassLoader()方法可以看到加載的范圍為
// String var0 = System.getProperty("java.ext.dirs");
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//獲取app類加載器镊叁,點(diǎn)進(jìn)getAppClassLoader()方法可以看到final String var1 = System.getProperty("java.class.path");
//注意此處傳入了var1,創(chuàng)建app時(shí)是尖,將ext傳入意系,跟進(jìn)代碼可以到頂層抽象類ClassLoader.java,代碼見(jiàn)下文
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//注意這個(gè)線程上下文類加載器饺汹,此加載器可以用戶JDBC蛔添、spring等打破雙親加載機(jī)制,默認(rèn)的線程上下文加載器 == app加載器
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);
}
}
private ClassLoader(Void unused, ClassLoader parent) {
//此時(shí)this指向app兜辞,parent指向ext迎瞧,且parent為該類的成員變量,這種關(guān)系沒(méi)有使用繼承逸吵,采用的基于松耦合的組合關(guān)系
// 由于啟動(dòng)類加載器是c++實(shí)現(xiàn)的凶硅,在java的視角里是沒(méi)有此對(duì)象的,所以為null扫皱;換而言之足绅,如果this指向是ext,那么他的parent為null韩脑,此處在loadClass方法中有體現(xiàn)氢妈,見(jiàn)下文代碼
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//看看這個(gè)findLoadedClass方法的位置,說(shuō)明相同的類只會(huì)加載一次
//這就是為很么修改了Class后段多,必須重新啟動(dòng)JVM首量,程序所做的修改才會(huì)生效的原因
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//父加載器不為空,就一直往上遞歸,直至交給頂層父類加載器加載加缘,結(jié)合上面代碼注釋可知鸭叙,從自定義加載器開(kāi)始,到app拣宏、到ext沈贝,都會(huì)遞歸往上找parent,直到parent == null
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//這個(gè)代碼是給頂層的啟動(dòng)類加載器調(diào)用的
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//進(jìn)行遞歸退出的處理
}
//從頂層開(kāi)始蚀浆,每一層類加載器加載不到缀程,就會(huì)逐步往下找可以加載的類加載器,所以自定義加載器是需要重寫(xiě)這個(gè)findClass方法的
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;
}
}
2.雙親委派模型中的"雙親"如何理解市俊?
并不是指它有兩個(gè)父類加載器的意思,一個(gè)類加載器只應(yīng)該有一個(gè)父加載器;
- 父類加載器(parent classloader):它可以替子加載器嘗試加載類滤奈;
他是整個(gè)JVM加載器的Java代碼可以訪問(wèn)到的類加載器的最頂端摆昧,即是超級(jí)父加載器,拓展類加載器是沒(méi)有父類加載器的蜒程,他的parent為null绅你,由上文loadclass代碼可知,為空就會(huì)調(diào)用findBootstrapClassOrNull()
昭躺。- 引導(dǎo)類加載器(bootstrap classloader): 子類加載器只能判斷某個(gè)類是否被引導(dǎo)類加載器加載過(guò)忌锯,而不能委托它加載某個(gè)類;換句話說(shuō)领炫,就是子類加載器不能接觸到引導(dǎo)類加載器偶垮,引導(dǎo)類加載器對(duì)其他類加載器而言是透明的。
3.為什么jdbc驅(qū)動(dòng)加載要打破雙親委派
因?yàn)镈river接口是java.sql包下的帝洪,根據(jù)規(guī)定似舵,這個(gè)應(yīng)該是bootstrap classloader來(lái)加載,但是具體的實(shí)現(xiàn)是各個(gè)廠商來(lái)做的葱峡,bootstrap classloader無(wú)法加載實(shí)現(xiàn)類砚哗,這個(gè)時(shí)候就需要子類加載器加載(加載過(guò)程是逐步往上遞歸,父類加載不到逐步由子類來(lái)加載砰奕,由此時(shí)直接交給app來(lái)加載不就行了么蛛芥,返回為null,直接走app classloader 的findclass方法進(jìn)行加載不就可以么)
針對(duì)上面?zhèn)€問(wèn)題军援,重新捋下邏輯
1.jdbc的加載是由DriverManager來(lái)執(zhí)行仅淑,這個(gè)類在rt下面,由根加載器來(lái)加載
2.該類有個(gè)靜態(tài)代碼塊盖溺,里面有l(wèi)oadInitialDrivers()方法漓糙,然后跟進(jìn)方法里面可以看到 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
//這兒涉及spi機(jī)制來(lái)加載廠商的實(shí)現(xiàn)
3.廠商的代碼肯定不在rt包里面,目前的加載器是根加載器烘嘱,無(wú)法加載廠商實(shí)現(xiàn)昆禽,jdk的方案是代碼如下:public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
可以看到是在當(dāng)前根加載器執(zhí)行加載期間蝗蛙,從當(dāng)前線程獲取到上線文加載器,去加載廠商的實(shí)現(xiàn)類
我的問(wèn)題來(lái)了:這兒如果不用上下文來(lái)加載醉鳖,直接返回加載不到捡硅,return null; 然后直接走app classloader 的findclass方法進(jìn)行加載不就可以么?
答:被這個(gè)問(wèn)題困擾了幾天,想不通盗棵,后面猜測(cè)如果返回為空壮韭,那之前根加載到的DriverManager怎么辦,是直接放棄了么纹因,我理解的是整個(gè)DriverManager加載和廠商驅(qū)動(dòng)加載是一體的原子性的喷屋,必須要在根加載器加載到一半的時(shí)候繼續(xù)去加載廠商的,如果這個(gè)時(shí)候返回為空瞭恰,那么后面app是可以加載廠商實(shí)現(xiàn)屯曹,但是DriverManager怎么辦,不加載了么惊畏,要加載的話恶耽,又是根加載器來(lái)加載
這個(gè)理解不知道對(duì)不對(duì)
4.為什么tomcat要打破雙親委派模型的?
從問(wèn)題1我們知道颜启,類的加載是逐步委托給頂層的類加載器進(jìn)行加載偷俭,加載不到時(shí)才會(huì)由下面的加載器進(jìn)行加載;
假設(shè)我們tomcat容器中有三個(gè)server缰盏,server1的jackson是1.0涌萤;server2的jackson是2.0;server3的jackson是3.0乳规;
三個(gè)版本里面的某個(gè)類的方法里面的實(shí)現(xiàn)可能不一樣形葬,但是由于雙親委派機(jī)制的存在,都會(huì)交由頂層類加載器來(lái)加載暮的,如果沒(méi)有自定義笙以,那么應(yīng)該是app加載,就會(huì)導(dǎo)致該類最終會(huì)被覆蓋成某個(gè)版本冻辩;(類在jvm中的唯一性由類加載器名和類全路徑?jīng)Q定)
因?yàn)槲覀兊娜齻€(gè)server是不同的應(yīng)用猖腕,所以我們需要各個(gè)server進(jìn)行隔離,獨(dú)自加載恨闪,不應(yīng)該使用雙親委派交由頂層加載
- tomcat是如何打破雙親委派模型的?
首先推薦一篇優(yōu)秀的文章 [Tomcat 的架構(gòu)有哪些牛逼之處倘感?](https://mp.weixin.qq.com/s/_bsAOTA10fGDJsz2jCYivg) 從架構(gòu)、實(shí)現(xiàn)等多個(gè)維度層面進(jìn)行分析咙咽,文章較難老玛,需要多次閱讀
org.apache.catalina.loader.WebappClassLoader
,該類重寫(xiě)了findclass方法
ublic Class<?> findClass(String name) throws ClassNotFoundException {
...
Class<?> clazz = null;
try {
//1. 先在 Web 應(yīng)用目錄下查找類
clazz = findClassInternal(name);
} catch (RuntimeException e) {
throw e;
}
if (clazz == null) {
try {
//2. 如果在本地目錄沒(méi)有找到,交給父加載器去查找
clazz = super.findClass(name);
} catch (RuntimeException e) {
throw e;
}
//3. 如果父類也沒(méi)找到蜡豹,拋出 ClassNotFoundException
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
同樣的也重寫(xiě)了loadclass方法
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
//1. 先在本地 cache 查找該類是否已經(jīng)加載過(guò)(主要是tomcat自定義加載器中查找)
clazz = findLoadedClass0(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
//2. 從系統(tǒng)類加載器的 cache 中查找是否加載過(guò)(從jvm加載的類里面查找)
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
// 3. 嘗試用 ExtClassLoader 類加載器類加載(此處是精髓麸粮,注意是ext,不是app镜廉,基礎(chǔ)類和擴(kuò)展類都是交由雙親來(lái)加載弄诲,避免了覆蓋 JRE 核心類,保證虛擬機(jī)的正常運(yùn)行)
ClassLoader javaseLoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 4. 嘗試在本地目錄搜索 class 并加載(這兒就是tomcat自定義加載器加載了)
try {
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 5. 嘗試用系統(tǒng)類加載器 (也就是 AppClassLoader) 來(lái)加載
(其他類還有交由app加載)
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
//6. 上述過(guò)程都加載失敗娇唯,拋出異常
throw new ClassNotFoundException(name);
}
WebAppClassLoader
Tomcat 的解決方案是自定義一個(gè)類加載器 WebAppClassLoader齐遵, 并且給每個(gè) Web 應(yīng)用創(chuàng)建一個(gè)類加載器實(shí)例。我們知道塔插,Context 容器組件對(duì)應(yīng)一個(gè) Web 應(yīng)用梗摇,因此,每個(gè) Context容器負(fù)責(zé)創(chuàng)建和維護(hù)一個(gè) WebAppClassLoader加載器實(shí)例佑淀。這背后的原理是留美,不同的加載器實(shí)例加載的類被認(rèn)為是不同的類,即使它們的類名相同伸刃。這就相當(dāng)于在 Java 虛擬機(jī)內(nèi)部創(chuàng)建了一個(gè)個(gè)相互隔離的 Java 類空間,每一個(gè) Web 應(yīng)用都有自己的類空間逢倍,Web 應(yīng)用之間通過(guò)各自的類加載器互相隔離捧颅。SharedClassLoader
本質(zhì)需求是兩個(gè) Web 應(yīng)用之間怎么共享庫(kù)類,并且不能重復(fù)加載相同的類。在雙親委托機(jī)制里较雕,各個(gè)子加載器都可以通過(guò)父加載器去加載類碉哑,那么把需要共享的類放到父加載器的加載路徑下不就行了嗎。
因此 Tomcat 的設(shè)計(jì)者又加了一個(gè)類加載器 SharedClassLoader亮蒋,作為 WebAppClassLoader的父加載器扣典,專門來(lái)加載 Web 應(yīng)用之間共享的類。如果 WebAppClassLoader自己沒(méi)有加載到某個(gè)類慎玖,就會(huì)委托父加載器 SharedClassLoader去加載這個(gè)類贮尖,SharedClassLoader會(huì)在指定目錄下加載共享類,之后返回給 WebAppClassLoader趁怔,這樣共享的問(wèn)題就解決了湿硝。CatalinaClassloader
如何隔離 Tomcat 本身的類和 Web 應(yīng)用的類?
要共享可以通過(guò)父子關(guān)系润努,要隔離那就需要兄弟關(guān)系了关斜。兄弟關(guān)系就是指兩個(gè)類加載器是平行的,它們可能擁有同一個(gè)父加載器铺浇,基于此 Tomcat 又設(shè)計(jì)一個(gè)類加載器 CatalinaClassloader痢畜,專門來(lái)加載 Tomcat 自身的類。
這樣設(shè)計(jì)有個(gè)問(wèn)題,那 Tomcat 和各 Web 應(yīng)用之間需要共享一些類時(shí)該怎么辦呢丁稀?
老辦法吼拥,還是再增加一個(gè) CommonClassLoader,作為 CatalinaClassloader和 SharedClassLoader的父加載器二驰。CommonClassLoader能加載的類都可以被 CatalinaClassLoader和 SharedClassLoader使用
//TODO
6.類加載成功后會(huì)立即調(diào)用構(gòu)造函數(shù)進(jìn)行實(shí)例化么
不會(huì)扔罪,如果涉及到new等創(chuàng)建對(duì)象才會(huì)實(shí)例化,類加載后是會(huì)初始化靜態(tài)代碼塊桶雀,靜態(tài)變量等矿酵,類的初始化和對(duì)象的初始化是兩個(gè)事情
7.自定義類加載器是如何保障加載器執(zhí)行順序的,即app怎么成為自定義加載器的parent
ClassLoader類里面默認(rèn)的parent是app
protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); }
8.當(dāng)實(shí)現(xiàn)自定義類加載器時(shí)不應(yīng)重寫(xiě)loadClass()矗积,除非你不需要雙親委派機(jī)制全肮。要重寫(xiě)的是findClass()的邏輯,也就是尋找并加載類的方式
總結(jié):
- 參考classloader里面申明 private final ClassLoader parent;然后子類基于組合的集成方式棘捣,之后遞歸雙親委派【從這兒我們可以抄作業(yè)辜腺,就是我們?cè)O(shè)計(jì)底層框架,借鑒這個(gè)模式乍恐,從而實(shí)現(xiàn)父類調(diào)用子類的方
- tomcat架構(gòu)運(yùn)用模板方法模式评疗。分別運(yùn)用了組合模式、觀察者模式茵烈、骨架抽象類和模板方法百匆,需要自己去體會(huì)如何使用的,使用和不使用的區(qū)別