阿里二面:雙親委派機(jī)制?原理?能打破嗎?
話不多說,開搞。
什么是雙親委派機(jī)制?
1、理解概述
雙親委派機(jī)制(Parent Delegation Model)是Java虛擬機(jī)(JVM)中的一種類加載機(jī)制。它是一種層次化的類加載器結(jié)構(gòu),通過委派給父類加載器來加載類,以保證類的唯一性和安全性。
在Java中,每個(gè)類都需要在運(yùn)行時(shí)被加載到內(nèi)存中才能被使用。類加載器負(fù)責(zé)將類的字節(jié)碼加載到內(nèi)存中,并生成對(duì)應(yīng)的Class對(duì)象。雙親委派機(jī)制是一種類加載器的工作方式,它通過一種層次化的結(jié)構(gòu)來加載類,保證類的加載是有序、唯一且安全的。
2、類加載過程
類加載過程是將類的字節(jié)碼加載到內(nèi)存中,并生成對(duì)應(yīng)的Class對(duì)象的過程。類加載過程主要包括以下幾個(gè)步驟:
- 加載(Loading):類加載的第一個(gè)階段是加載,即將類的字節(jié)碼文件加載到內(nèi)存中。加載階段由類加載器完成,類加載器根據(jù)類的全限定名(包括包名和類名)來定位并讀取字節(jié)碼文件。加載階段的結(jié)果是在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象。
- 驗(yàn)證(Verification):在驗(yàn)證階段,對(duì)加載的字節(jié)碼進(jìn)行驗(yàn)證,確保字節(jié)碼的正確性和安全性。驗(yàn)證階段包括以下幾個(gè)方面的驗(yàn)證:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。
- 準(zhǔn)備(Preparation):在準(zhǔn)備階段,為類的靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)初始值。這些變量包括靜態(tài)變量和靜態(tài)常量。
- 解析(Resolution):在解析階段,將符號(hào)引用解析為直接引用。符號(hào)引用是一種符號(hào)名稱,可以是類、字段、方法等的引用。直接引用是直接指向內(nèi)存中的地址,可以是指向方法區(qū)中的方法、字段等的指針。
- 初始化(Initialization):在初始化階段,對(duì)類進(jìn)行初始化,包括執(zhí)行靜態(tài)變量的賦值和靜態(tài)代碼塊的執(zhí)行。初始化階段是類加載過程的最后一個(gè)階段,只有在初始化階段完成后,類才能被正常使用。
需要注意的是,類加載過程是按需加載的,即在首次使用類時(shí)才會(huì)進(jìn)行加載。而且類加載過程是線程安全的,即同一個(gè)類在多線程環(huán)境下只會(huì)被加載一次。
另外,類加載過程可以由自定義的類加載器來擴(kuò)展或修改,默認(rèn)的類加載器是應(yīng)用程序類加載器(Application ClassLoader),它負(fù)責(zé)加載應(yīng)用程序的類。自定義類加載器可以實(shí)現(xiàn)一些特定的需求,如加載加密的字節(jié)碼文件、從網(wǎng)絡(luò)或其他非標(biāo)準(zhǔn)位置加載類等。
反映在雙親委派機(jī)制上:
圖片
具體來說,當(dāng)一個(gè)類加載器收到加載類的請(qǐng)求時(shí),它會(huì)先檢查自己是否已經(jīng)加載過這個(gè)類。如果已經(jīng)加載過,則直接返回已加載的類。如果沒有加載過,則將加載請(qǐng)求委派給它的父類加載器。
父類加載器會(huì)按照同樣的方式繼續(xù)檢查是否已經(jīng)加載過這個(gè)類,直到達(dá)到頂層的啟動(dòng)類加載器。如果所有的父類加載器都無法加載這個(gè)類,則由當(dāng)前類加載器自己去加載。如果加載成功,則將生成的Class對(duì)象返回給請(qǐng)求者。
3、類加載器的層次結(jié)構(gòu)
類加載器的層次結(jié)構(gòu)是指類加載器之間的父子關(guān)系,它們按照一定的順序組成了一個(gè)層次化的結(jié)構(gòu)。在Java中,雙親委派機(jī)制的類加載器結(jié)構(gòu)一般包括三個(gè)層次:
- 啟動(dòng)類加載器(Bootstrap ClassLoader):也稱為根類加載器,它是虛擬機(jī)的一部分,通常由本地代碼實(shí)現(xiàn),不是Java類。它負(fù)責(zé)加載Java的核心類庫,如java.lang包中的類。啟動(dòng)類加載器是所有其他類加載器的父加載器,它沒有父加載器。
- 擴(kuò)展類加載器(Extension ClassLoader):它是由Java編寫的類,是由啟動(dòng)類加載器加載的。擴(kuò)展類加載器負(fù)責(zé)加載Java的擴(kuò)展類庫,如javax包中的類。它的父加載器是啟動(dòng)類加載器。
- 應(yīng)用程序類加載器(Application ClassLoader):也稱為系統(tǒng)類加載器,它負(fù)責(zé)加載應(yīng)用程序的類,即開發(fā)者自己編寫的類。應(yīng)用程序類加載器是由擴(kuò)展類加載器加載的。它的父加載器是擴(kuò)展類加載器。
除了這三個(gè)主要的類加載器,還有一些其他的類加載器,如自定義的類加載器。自定義的類加載器可以繼承自應(yīng)用程序類加載器或其他類加載器,形成更復(fù)雜的層次結(jié)構(gòu)。
類加載器的層次結(jié)構(gòu)是通過雙親委派機(jī)制來實(shí)現(xiàn)的。當(dāng)一個(gè)類加載器收到加載類的請(qǐng)求時(shí),它會(huì)先向上委派給父類加載器,由父類加載器嘗試加載。
父類加載器會(huì)按照同樣的方式繼續(xù)向上委派,直到達(dá)到頂層的啟動(dòng)類加載器。如果所有的父類加載器都無法加載這個(gè)類,則由當(dāng)前類加載器自己去加載。
這樣的層次結(jié)構(gòu)保證了類加載的一致性和安全性,避免了類的重復(fù)加載和惡意代碼的替換。
雙親委派機(jī)制的作用是什么?
雙親委派機(jī)制的作用是保證Java類的加載的一致性和安全性;具體來說,雙親委派機(jī)制的作用有以下幾個(gè)方面:
- 避免重復(fù)加載:當(dāng)一個(gè)類加載器收到加載類的請(qǐng)求時(shí),它會(huì)先向上委派給父類加載器,由父類加載器嘗試加載。父類加載器會(huì)按照同樣的方式繼續(xù)向上委派,直到達(dá)到頂層的啟動(dòng)類加載器。如果所有的父類加載器都無法加載這個(gè)類,則由當(dāng)前類加載器自己去加載。這樣的層次結(jié)構(gòu)保證了類的加載只會(huì)發(fā)生一次,避免了重復(fù)加載。
- 類的隔離性:每個(gè)類加載器都有自己的命名空間,它只能加載自己命名空間下的類。當(dāng)一個(gè)類加載器嘗試加載一個(gè)類時(shí),它會(huì)先檢查自己的命名空間中是否已經(jīng)加載了這個(gè)類。如果已經(jīng)加載,則直接返回已加載的類;如果沒有加載,則委派給父類加載器加載。這樣的隔離性保證了不同類加載器加載的類之間互不影響,避免了類的沖突和版本不一致的問題。
- 安全性:通過雙親委派機(jī)制,Java類的加載可以由更高層次的類加載器來完成,這些類加載器通常是由Java官方或其他可信任的實(shí)體提供的。這樣可以確保核心類庫和擴(kuò)展類庫的安全性,避免了惡意代碼的替換和執(zhí)行。
總的來說,雙親委派機(jī)制通過層次化的類加載器結(jié)構(gòu),保證了Java類的加載的一致性和安全性,避免了重復(fù)加載和類的沖突,同時(shí)也提供了一種安全的加載機(jī)制,防止惡意代碼的執(zhí)行。
然而,雙親委派機(jī)制也有一些缺點(diǎn):
- 限制了類加載的靈活性:雙親委派機(jī)制要求類加載器按照一定的順序去加載類,這限制了類加載的靈活性。有時(shí)候,我們可能需要自定義的類加載器來加載特定的類,但由于雙親委派機(jī)制的存在,這些類可能會(huì)被父類加載器加載,無法實(shí)現(xiàn)自定義加載的需求。
- 難以實(shí)現(xiàn)類的動(dòng)態(tài)更新:由于雙親委派機(jī)制的存在,當(dāng)一個(gè)類被加載后,它的類定義就不能再被修改。這意味著如果我們想要在運(yùn)行時(shí)動(dòng)態(tài)更新一個(gè)類的定義,就需要重新加載整個(gè)類及其依賴的類。這對(duì)于一些需要頻繁更新的應(yīng)用場(chǎng)景來說,可能會(huì)帶來一些困擾。
總的來說,雙親委派機(jī)制在保證類加載的一致性和安全性方面具有明顯的優(yōu)勢(shì),但也存在一定的限制和缺點(diǎn)。在實(shí)際應(yīng)用中,需要根據(jù)具體的需求來權(quán)衡使用雙親委派機(jī)制的利與弊。
雙親委派機(jī)制的工作原理是什么?
雙親委派機(jī)制的工作原理可以簡(jiǎn)單概括為以下幾個(gè)步驟:
- 當(dāng)一個(gè)類加載器收到加載類的請(qǐng)求時(shí),它會(huì)先檢查自己的命名空間中是否已經(jīng)加載了這個(gè)類。如果已經(jīng)加載,則直接返回已加載的類;如果沒有加載,則繼續(xù)下一步。
- 類加載器會(huì)將加載請(qǐng)求委派給父類加載器。父類加載器會(huì)按照同樣的方式繼續(xù)向上委派,直到達(dá)到頂層的啟動(dòng)類加載器。
- 如果所有的父類加載器都無法加載這個(gè)類,則由當(dāng)前類加載器自己去加載。當(dāng)前類加載器會(huì)根據(jù)自己的加載策略,從指定的路徑或資源中加載類的字節(jié)碼,并將其轉(zhuǎn)換為可執(zhí)行的類。
- 加載完成后,將加載的類及其相關(guān)信息存放在當(dāng)前類加載器的命名空間中,以便后續(xù)的類加載請(qǐng)求可以直接使用。
通過這樣的層次結(jié)構(gòu)和委派機(jī)制,雙親委派機(jī)制保證了類的加載只會(huì)發(fā)生一次,避免了重復(fù)加載;同時(shí),也保證了類的隔離性,不同類加載器加載的類之間互不影響;還能提供一種安全的加載機(jī)制,防止惡意代碼的執(zhí)行。
需要注意的是,雙親委派機(jī)制并不是強(qiáng)制性的,可以根據(jù)具體的需求進(jìn)行調(diào)整或擴(kuò)展。在Java中,可以通過自定義類加載器來改變類加載的行為,實(shí)現(xiàn)一些特定的需求。
這樣可以使得:
- 避免類的重復(fù)加載:通過委派給父類加載器,可以避免同一個(gè)類被多個(gè)類加載器加載,節(jié)省了內(nèi)存空間。
- 提高類加載的效率:父類加載器已經(jīng)加載過的類可以直接返回,無需再次加載,提高了類加載的效率。
- 提高Java程序的安全性:防止惡意代碼替換核心類庫,提高了Java程序的安全性。
然而,有時(shí)候我們需要打破雙親委派機(jī)制,自定義類加載器來實(shí)現(xiàn)特定的需求。這需要謹(jǐn)慎操作,因?yàn)榇蚱齐p親委派機(jī)制可能導(dǎo)致類加載的混亂和安全性問題。
打破雙親委派機(jī)制?
為什么要打破雙親委派機(jī)制嗎?
打破雙親委派機(jī)制的主要原因是為了滿足一些特定的需求和場(chǎng)景,例如:
- 實(shí)現(xiàn)類的熱部署:在某些應(yīng)用場(chǎng)景下,需要在運(yùn)行時(shí)動(dòng)態(tài)加載和替換類,以實(shí)現(xiàn)熱部署的功能。而雙親委派機(jī)制會(huì)導(dǎo)致類的加載只發(fā)生一次,無法實(shí)現(xiàn)類的熱替換。通過打破雙親委派機(jī)制,可以自定義類加載器,在需要時(shí)重新加載和替換類。
- 加載非標(biāo)準(zhǔn)的類文件:有些特殊的類文件,如動(dòng)態(tài)生成的字節(jié)碼、非標(biāo)準(zhǔn)的類文件格式等,無法通過標(biāo)準(zhǔn)的類加載器加載。通過打破雙親委派機(jī)制,可以自定義類加載器,實(shí)現(xiàn)對(duì)這些非標(biāo)準(zhǔn)類文件的加載和解析。
- 實(shí)現(xiàn)類加載的動(dòng)態(tài)控制:有些應(yīng)用需要對(duì)類的加載進(jìn)行特殊的控制,例如對(duì)特定的類進(jìn)行加密、解密或驗(yàn)證等操作。通過打破雙親委派機(jī)制,可以自定義類加載器,在加載類時(shí)進(jìn)行特殊的處理。
需要注意的是,打破雙親委派機(jī)制可能會(huì)引入一些潛在的風(fēng)險(xiǎn)和問題,如類的沖突、不一致性等。因此,在打破雙親委派機(jī)制時(shí),需要謹(jǐn)慎考慮,并確保自定義的類加載器能夠正確處理類的加載和依賴關(guān)系。
怎樣打破雙親委派機(jī)制?
在Java中,有以下幾種方法可以打破雙親委派機(jī)制:
- 自定義類加載器:通過自定義ClassLoader的子類,重寫findClass()方法,實(shí)現(xiàn)自定義的類加載邏輯。在自定義類加載器中,可以選擇不委派給父類加載器,而是自己去加載類。
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 自定義類加載邏輯,例如從特定路徑加載類文件
byte[] classBytes = loadClassBytes(name);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] loadClassBytes(String name) {
// 從特定路徑加載類文件,并返回字節(jié)碼
// ...
}
}
- 線程上下文類加載器:通過Thread類的setContextClassLoader()方法,可以設(shè)置線程的上下文類加載器。在某些框架或庫中,會(huì)使用線程上下文類加載器來加載特定的類,從而打破雙親委派機(jī)制。
- OSGi框架:OSGi(Open Service Gateway Initiative)是一種動(dòng)態(tài)模塊化的Java平臺(tái),它提供了一套機(jī)制來管理和加載模塊。在OSGi中,每個(gè)模塊都有自己的類加載器,可以獨(dú)立加載和管理類,從而打破雙親委派機(jī)制。
- Java SPI機(jī)制:Java SPI(Service Provider Interface)是一種標(biāo)準(zhǔn)的服務(wù)發(fā)現(xiàn)機(jī)制,在SPI中,服務(wù)的實(shí)現(xiàn)類通過在META-INF/services目錄下的配置文件中聲明,而不是通過類路徑來查找。通過SPI機(jī)制,可以實(shí)現(xiàn)在不同的類加載器中加載不同的服務(wù)實(shí)現(xiàn)類,從而打破雙親委派機(jī)制。
需要注意的是,打破雙親委派機(jī)制可能會(huì)引入一些潛在的風(fēng)險(xiǎn)和問題,如類的沖突、不一致性等。在使用這些方法打破雙親委派機(jī)制時(shí),需要謹(jǐn)慎考慮,并確保能夠正確處理類的加載和依賴關(guān)系。
除了上述提到的方法,還有一些其他的方法可以打破雙親委派機(jī)制:
- 使用Java Instrumentation API:Java Instrumentation API允許在類加載過程中修改字節(jié)碼,從而可以在類加載時(shí)修改類的加載行為,包括打破雙親委派機(jī)制。通過Instrumentation API,可以在類加載前修改類的字節(jié)碼,使其加載時(shí)使用自定義的類加載器。
- 使用Java動(dòng)態(tài)代理:Java動(dòng)態(tài)代理機(jī)制可以在運(yùn)行時(shí)生成代理類,并在代理類中實(shí)現(xiàn)特定的邏輯。通過使用動(dòng)態(tài)代理,可以在類加載時(shí)動(dòng)態(tài)生成代理類,并在代理類中實(shí)現(xiàn)自定義的類加載邏輯,從而打破雙親委派機(jī)制。
- 使用字節(jié)碼操作庫:可以使用字節(jié)碼操作庫,如ASM、Javassist等,來直接操作字節(jié)碼,從而修改類的加載行為。通過這些庫,可以在類加載時(shí)修改字節(jié)碼,使其加載時(shí)使用自定義的類加載器。
在某些框架或場(chǎng)景中,為了滿足特定的需求,可能會(huì)打破雙親委派機(jī)制。以下是一些常見的框架或場(chǎng)景:
- JavaEE容器:JavaEE容器(如Tomcat、WebLogic、WebSphere等)通常會(huì)使用自定義的類加載器來加載應(yīng)用程序的類,以實(shí)現(xiàn)應(yīng)用程序的隔離和獨(dú)立性。這些容器會(huì)打破雙親委派機(jī)制,使用自定義的類加載器來加載應(yīng)用程序的類。
- OSGi框架:OSGi(Open Service Gateway Initiative)是一種動(dòng)態(tài)模塊化的Java平臺(tái),它提供了一套機(jī)制來管理和加載模塊。在OSGi中,每個(gè)模塊都有自己的類加載器,可以獨(dú)立加載和管理類,從而打破雙親委派機(jī)制。
- Java SPI機(jī)制:Java SPI(Service Provider Interface)是一種標(biāo)準(zhǔn)的服務(wù)發(fā)現(xiàn)機(jī)制,在SPI中,服務(wù)的實(shí)現(xiàn)類通過在META-INF/services目錄下的配置文件中聲明,而不是通過類路徑來查找。通過SPI機(jī)制,可以實(shí)現(xiàn)在不同的類加載器中加載不同的服務(wù)實(shí)現(xiàn)類,從而打破雙親委派機(jī)制。
- 動(dòng)態(tài)代理框架:一些動(dòng)態(tài)代理框架,如CGLIB、Byte Buddy等,可以在運(yùn)行時(shí)生成代理類,并在代理類中實(shí)現(xiàn)特定的邏輯。這些框架通常會(huì)使用自定義的類加載器來加載生成的代理類,從而打破雙親委派機(jī)制
總結(jié)
雙親委派機(jī)制是Java類加載器的一種工作機(jī)制,它的核心思想是在類加載的過程中,優(yōu)先將加載請(qǐng)求委派給父類加載器,只有在父類加載器無法加載時(shí),才由子類加載器嘗試加載。
雙親委派機(jī)制的主要特點(diǎn)和優(yōu)勢(shì)包括:
- 避免類的重復(fù)加載:當(dāng)一個(gè)類被加載后,它會(huì)被父類加載器緩存起來,避免了重復(fù)加載同一個(gè)類的問題,提高了類加載的效率。
- 類的隔離和安全性:通過雙親委派機(jī)制,不同的類加載器加載的類具有不同的命名空間,相同類名的類可以被不同的類加載器加載,實(shí)現(xiàn)了類的隔離和安全性。
- 保護(hù)核心類庫的完整性:核心類庫由啟動(dòng)類加載器加載,避免了用戶自定義的類替換核心類庫的情況,保護(hù)了核心類庫的完整性。
總結(jié)起來:
- 雙親委派機(jī)制通過層級(jí)結(jié)構(gòu)的類加載器組織,實(shí)現(xiàn)了類的共享、隔離和安全性。
- 它是Java類加載器的一種重要機(jī)制,為Java應(yīng)用程序提供了良好的類加載環(huán)境。
- 然而,在某些特定的場(chǎng)景下,為了滿足特定的需求,可能需要打破雙親委派機(jī)制,使用自定義的類加載器來加載類。
- 在使用自定義類加載器時(shí),需要仔細(xì)評(píng)估和測(cè)試,確保能夠正確處理類的加載和依賴關(guān)系。