JVM優(yōu)化:雙親委派模型
一、什么是雙親委派
雙親委派模型工作過(guò)程是:如果一個(gè)類(lèi)加載器收到類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把這個(gè) 請(qǐng)求委派給父類(lèi)加載器完成。每個(gè)類(lèi)加載器都是如此,只有當(dāng)父加載器在自己的搜索范圍內(nèi)找不到指定的類(lèi)時(shí) (即 ClassNotFoundException ),子加載器才會(huì)嘗試自己去加載。
二、為什么需要雙親委派模型?
為什么需要雙親委派模型呢?假設(shè)沒(méi)有雙親委派模型,試想一個(gè)場(chǎng)景:
黑客自定義一個(gè) java.lang.String 類(lèi),該 String 類(lèi)具有系統(tǒng)的 String 類(lèi)一樣的功能,只是在某個(gè)函數(shù) 稍作修改。比如 equals 函數(shù),這個(gè)函數(shù)經(jīng)常使用,如果在這這個(gè)函數(shù)中,黑客加入一些“病毒代碼”。并且 通過(guò)自定義類(lèi)加載器加入到 JVM 中。此時(shí),如果沒(méi)有雙親委派模型,那么 JVM 就可能誤以為黑客自定義的 java.lang.String 類(lèi)是系統(tǒng)的 String 類(lèi),導(dǎo)致“病毒代碼”被執(zhí)行。
而有了雙親委派模型,黑客自定義的 java.lang.String 類(lèi)永遠(yuǎn)都不會(huì)被加載進(jìn)內(nèi)存。因?yàn)槭紫仁亲铐敹说念?lèi)加 載器加載系統(tǒng)的 java.lang.String 類(lèi),最終自定義的類(lèi)加載器無(wú)法加載 java.lang.String 類(lèi)。
或許你會(huì)想,我在自定義的類(lèi)加載器里面強(qiáng)制加載自定義的 java.lang.String 類(lèi),不去通過(guò)調(diào)用父加載器不就 好了嗎?確實(shí),這樣是可行。但是,在 JVM 中,判斷一個(gè)對(duì)象是否是某個(gè)類(lèi)型時(shí),如果該對(duì)象的實(shí)際類(lèi)型與待比較 的類(lèi)型的類(lèi)加載器不同,那么會(huì)返回false。
舉個(gè)栗子:
ClassLoader1 、 ClassLoader2 都加載 java.lang.String 類(lèi),對(duì)應(yīng)Class1、Class2對(duì)象。那么 Class1 對(duì)象不屬于 ClassLoad2 對(duì)象加載的 java.lang.String 類(lèi)型。
三、如何實(shí)現(xiàn)雙親委派模型
雙親委派模型的原理很簡(jiǎn)單,實(shí)現(xiàn)也簡(jiǎn)單。每次通過(guò)先委托父類(lèi)加載器加載,當(dāng)父類(lèi)加載器無(wú)法加載時(shí),再自己加 載。其實(shí) ClassLoader 類(lèi)默認(rèn)的 loadClass 方法已經(jīng)幫我們寫(xiě)好了,我們無(wú)需去寫(xiě)。
幾個(gè)重要函數(shù)
loadClass 默認(rèn)實(shí)現(xiàn)如下:
public Class loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
再看看 loadClass(String name, boolean resolve) 函數(shù):
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;
}
}
從上面代碼可以明顯看出, loadClass(String, boolean) 函數(shù)即實(shí)現(xiàn)了雙親委派模型!整個(gè)大致過(guò)程如下:
1. 首先,檢查一下指定名稱(chēng)的類(lèi)是否已經(jīng)加載過(guò),如果加載過(guò)了,就不需要再加載,直接返回。
2. 如果此類(lèi)沒(méi)有加載過(guò),那么,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即 調(diào)用 parent.loadClass(name, false); ).或者是調(diào)用 bootstrap 類(lèi)加載器來(lái)加載。
3. 如果父加載器及 bootstrap 類(lèi)加載器都沒(méi)有找到指定的類(lèi),那么調(diào)用當(dāng)前類(lèi)加載器的 findClass 方 法來(lái)完成類(lèi)加載。
換句話說(shuō),如果自定義類(lèi)加載器,就必須重寫(xiě) findClass 方法!
findClass 的默認(rèn)實(shí)現(xiàn)如下:
protected Class findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看出,抽象類(lèi) ClassLoader 的 findClass 函數(shù)默認(rèn)是拋出異常的。而前面我們知道, loadClass 在父加載 器無(wú)法加載類(lèi)的時(shí)候,就會(huì)調(diào)用我們自定義的類(lèi)加載器中的 findeClass 函數(shù),因此我們必須要在 loadClass 這 個(gè)函數(shù)里面實(shí)現(xiàn)將一個(gè)指定類(lèi)名稱(chēng)轉(zhuǎn)換為 Class 對(duì)象。
如果是讀取一個(gè)指定的名稱(chēng)的類(lèi)為字節(jié)數(shù)組的話,這很好辦。但是如何將字節(jié)數(shù)組轉(zhuǎn)為 Class 對(duì)象呢?很簡(jiǎn)單, Java 提供了 defineClass 方法,通過(guò)這個(gè)方法,就可以把一個(gè)字節(jié)數(shù)組轉(zhuǎn)為Class對(duì)象。
defineClass 主要的功能是:
將一個(gè)字節(jié)數(shù)組轉(zhuǎn)為 Class 對(duì)象,這個(gè)字節(jié)數(shù)組是 class 文件讀取后最終的字節(jié)數(shù)組。如,假設(shè) class 文 件是加密過(guò)的,則需要解密后作為形參傳入 defineClass 函數(shù)。
defineClass 默認(rèn)實(shí)現(xiàn)如下:
protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
return defineClass(name, b, off, len, null);