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