Java中ClassLoader類(lèi)加載學(xué)習(xí)總結(jié)
雙親委派模型
類(lèi)加載這個(gè)概念應(yīng)該算是Java語(yǔ)言的一種創(chuàng)新,目的是為了將類(lèi)的加載過(guò)程與虛擬機(jī)解耦,達(dá)到”通過(guò)類(lèi)的全限定名來(lái)獲取描述此類(lèi)的二進(jìn)制字節(jié)流“的目的。實(shí)現(xiàn)這個(gè)功能的代碼模塊就是類(lèi)加載器。類(lèi)加載器的基本模型就是大名鼎鼎的雙親委派模型(Parents Delegation Model)。聽(tīng)上去很牛掰,其實(shí)邏輯很簡(jiǎn)單,在需要加載一個(gè)類(lèi)的時(shí)候,我們首先判斷該類(lèi)是否已被加載,如果沒(méi)有就判斷是否已被父加載器加載,如果還沒(méi)有再調(diào)用自己的findClass方法嘗試加載。基本的模型就是這樣(盜圖侵刪):

實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,重點(diǎn)就是ClassLoader類(lèi)的loadClass方法,源碼如下:
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) {
Class(c);
}
return c;
}
}
突然感覺(jué)被逗了,怎么默認(rèn)直接就拋了異常呢?其實(shí)是因?yàn)镃lassLoader這個(gè)類(lèi)是一個(gè)抽象類(lèi),實(shí)際在使用時(shí)候會(huì)寫(xiě)個(gè)子類(lèi),這個(gè)方法會(huì)按照需要被重寫(xiě),來(lái)完成業(yè)務(wù)需要的加載過(guò)程。
自定義ClassLoader
在自定義ClassLoader的子類(lèi)時(shí)候,我們常見(jiàn)的會(huì)有兩種做法,一種是重寫(xiě)loadClass方法,另一種是重寫(xiě)findClass方法。其實(shí)這兩種方法本質(zhì)上差不多,畢竟loadClass也會(huì)調(diào)用findClass,但是從邏輯上講我們最好不要直接修改loadClass的內(nèi)部邏輯。
個(gè)人認(rèn)為比較好的做法其實(shí)是只在findClass里重寫(xiě)自定義類(lèi)的加載方法。
為啥說(shuō)這種比較好呢,因?yàn)榍懊嫖乙舱f(shuō)道,loadClass這個(gè)方法是實(shí)現(xiàn)雙親委托模型邏輯的地方,擅自修改這個(gè)方法會(huì)導(dǎo)致模型被破壞,容易造成問(wèn)題。因此我們最好是在雙親委托模型框架內(nèi)進(jìn)行小范圍的改動(dòng),不破壞原有的穩(wěn)定結(jié)構(gòu)。同時(shí),也避免了自己重寫(xiě)loadClass方法的過(guò)程中必須寫(xiě)雙親委托的重復(fù)代碼,從代碼的復(fù)用性來(lái)看,不直接修改這個(gè)方法始終是比較好的選擇。
當(dāng)然,如果是刻意要破壞雙親委托模型就另說(shuō)。
破壞雙親委托模型
為什么要破壞雙親委托模型呢?
其實(shí)在某些情況下,我們可能需要加載兩個(gè)不同的類(lèi),但是不巧的是這兩個(gè)類(lèi)的名字完全一樣,這時(shí)候雙親委托模型就無(wú)法滿(mǎn)足我們的要求了,我們就要重寫(xiě)loadClass方法破壞雙親委托模型,讓同一個(gè)類(lèi)名加載多次。當(dāng)然,這里說(shuō)的破壞只是局部意義上的破壞。
但是類(lèi)名相同了,jvm怎么區(qū)別這兩個(gè)類(lèi)呢?顯然,這并不會(huì)造成什么世界觀(guān)的崩塌,其實(shí)類(lèi)在jvm里并不僅是通過(guò)類(lèi)名來(lái)限定的,他還屬于加載他的ClassLoader。由不同ClassLoader加載的類(lèi)其實(shí)是互不影響的。
做一個(gè)實(shí)驗(yàn)。
我們先寫(xiě)兩個(gè)類(lèi):
package com.mythsman.test;
public class Hello {
public void say() {
System.out.println("This is from Hello v1");
}
}
package com.mythsman.test;
public class Hello {
public void say() {
System.out.println("This is from Hello v2");
}
}
兩個(gè)類(lèi)名字一樣,唯一的區(qū)別是方法的實(shí)現(xiàn)不一樣。我們先分別編譯,然后把生成的class文件重命名為Hello.class.1和Hello.class.2。
我們的目的是希望能在測(cè)試類(lèi)里分別創(chuàng)建這兩個(gè)類(lèi)的實(shí)例。
接著我們新建一個(gè)測(cè)試類(lèi)com.mythsman.test.Main,在主函數(shù)里創(chuàng)建兩個(gè)自定義的ClassLoader:
ClassLoader classLoader1=new ClassLoader() {
@Override
public Class<?> loadClass(String s) throws ClassNotFoundException {
try {
if (s.equals("com.mythsman.test.Hello")) {
byte[] classBytes = Files.readAllBytes(Paths.get("/home/myths/Desktop/test/Hello.class.1"));
return defineClass(s, classBytes, 0, classBytes.length);
}else{
return super.loadClass(s);
}
}catch (IOException e) {
throw new ClassNotFoundException(s);
}
}
};
ClassLoader classLoader2=new ClassLoader() {
@Override
public Class<?> loadClass(String s) throws ClassNotFoundException {
try {
if (s.equals("com.mythsman.test.Hello")) {
byte[] classBytes = Files.readAllBytes(Paths.get("/home/myths/Desktop/test/Hello.class.2"));
return defineClass(s, classBytes, 0, classBytes.length);
}else{
return super.loadClass(s);
}
}catch (IOException e) {
throw new ClassNotFoundException(s);
}
}
};
這兩個(gè)ClassLoader的用途就是分別關(guān)聯(lián)Hello類(lèi)的兩種不同字節(jié)碼,我們需要讀取字節(jié)碼文件并通過(guò)defineClass方法加載成class。注意我們重載的是loadClass方法,如果是重載findClass方法那么由于loadClass方法的雙親委托處理機(jī)制,第二個(gè)ClassLoader的findClass方法其實(shí)并不會(huì)被調(diào)用。
那我們?cè)趺瓷蓪?shí)例呢?顯然我們不能直接用類(lèi)名來(lái)引用(名稱(chēng)沖突),那就只能用反射了:
Object helloV1=classLoader1.loadClass("com.mythsman.test.Hello").newInstance();
Object helloV2=classLoader2.loadClass("com.mythsman.test.Hello").newInstance();
helloV1.getClass().getMethod("say").invoke(helloV1);
helloV2.getClass().getMethod("say").invoke(helloV2);
輸出:
This is from Hello v1 This is from Hello v2
OK,這樣就算是完成了兩次加載,但是還有幾個(gè)注意點(diǎn)需要關(guān)注下。
兩個(gè)類(lèi)的關(guān)系是什么
顯然這兩個(gè)類(lèi)并不是同一個(gè)類(lèi),但是他們的名字一樣,那么類(lèi)似isinstance of之類(lèi)的操作符結(jié)果是什么樣的呢:
System.out.println("class:"+helloV1.getClass());
System.out.println("class:"+helloV2.getClass());
System.out.println("hashCode:"+helloV1.getClass().hashCode());
System.out.println("hashCode:"+helloV2.getClass().hashCode());
System.out.println("classLoader:"+helloV1.getClass().getClassLoader());
System.out.println("classLoader:"+helloV2.getClass().getClassLoader());
輸出:
class:class com.mythsman.test.Hello class:class com.mythsman.test.Hello hashCode:1581781576 hashCode:1725154839 classLoader:com.mythsman.test.Main$1@5e2de80c classLoader:com.mythsman.test.Main$2@266474c2
他們的類(lèi)名的確是一樣的,但是類(lèi)的hashcode不一樣,也就意味著這兩個(gè)本質(zhì)不是一個(gè)類(lèi),而且他們的類(lèi)加載器也不同(其實(shí)就是Main的兩個(gè)內(nèi)部類(lèi))。
這兩個(gè)類(lèi)加載器跟系統(tǒng)的三層類(lèi)加載器是什么關(guān)系
以第一個(gè)自定義的類(lèi)加載器為例:
System.out.println(classLoader1.getParent().getParent().getParent()); System.out.println(classLoader1.getParent().getParent()); System.out.println(classLoader1.getParent()); System.out.println(classLoader1 ); System.out.println(ClassLoader.getSystemClassLoader());
輸出:
null sun.misc.Launcher$ExtClassLoader@60e53b93 sun.misc.Launcher$AppClassLoader@18b4aac2 com.mythsman.test.Main$1@5e2de80c sun.misc.Launcher$AppClassLoader@18b4aac2
當(dāng)然,這里說(shuō)的父子關(guān)系并不是繼承關(guān)系,而是組合關(guān)系,子ClassLoader保存了父ClassLoader的一個(gè)引用(parent)。
- 深入解析Java中的Classloader的運(yùn)行機(jī)制
- Java中的ClassLoader類(lèi)加載器使用詳解
- Java類(lèi)加載器ClassLoader的使用詳解
- java ClassLoader機(jī)制詳細(xì)講解
- Java類(lèi)加載器ClassLoader用法解析
- Java運(yùn)行時(shí)環(huán)境之ClassLoader類(lèi)加載機(jī)制詳解
- Java Classloader機(jī)制用法代碼解析
- Java類(lèi)加載器ClassLoader詳解
- Java基礎(chǔ)之ClassLoader詳解
- Java classloader類(lèi)加載器的實(shí)現(xiàn)
相關(guān)文章
Java Stream 的 collect 與 reduce 
在 Java Stream API 中,collect?和?reduce?是兩種強(qiáng)大的終止操作,用于將流中的元素累積為最終結(jié)果,本文將從核心概念、使用場(chǎng)景、性能特性等多個(gè)維度進(jìn)行對(duì)比分析,感興趣的朋友跟隨小編一起看看吧2025-09-09
詳解spring cloud config整合gitlab搭建分布式的配置中心
這篇文章主要介紹了詳解spring cloud config整合gitlab搭建分布式的配置中心,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
java學(xué)生信息管理系統(tǒng)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了java學(xué)生信息管理系統(tǒng)設(shè)計(jì),學(xué)生信息添加進(jìn)入數(shù)據(jù)庫(kù)的事務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Nacos客戶(hù)端本地緩存和故障轉(zhuǎn)移方式
Nacos客戶(hù)端在從Server獲得服務(wù)時(shí),若出現(xiàn)故障,會(huì)通過(guò)ServiceInfoHolder和FailoverReactor進(jìn)行故障轉(zhuǎn)移,ServiceInfoHolder緩存服務(wù)信息,FailoverReactor處理故障轉(zhuǎn)移,包括開(kāi)啟故障轉(zhuǎn)移開(kāi)關(guān)、讀取備份文件等2024-12-12
SpringMVC域?qū)ο蠊蚕頂?shù)據(jù)示例詳解
這篇文章主要為大家介紹了SpringMVC域?qū)ο蠊蚕頂?shù)據(jù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了利用session實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
詳解JVM的內(nèi)存對(duì)象介紹[創(chuàng)建和訪(fǎng)問(wèn)]
這篇文章主要介紹了JVM的內(nèi)存對(duì)象介紹[創(chuàng)建和訪(fǎng)問(wèn)],文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03

