高級進階-jvm之雙親委派模型,看了發現如此簡單

一、 什麼是雙親委派模型?

到底什麼是雙親委派,我先引入一個形象的小故事,你就明白了。

背景:在一個飢寒交迫的年代,生活著小明一家人,有人送來一個蘋果。

爸爸把蘋果接過來,本著尊老愛幼的傳統美德,順手遞給了爺爺,對他說你吃吧,我不餓。

爺爺看了看蘋果,順手遞給了曾祖父(爺爺的爸爸),對他說你年齡大了,

你想吃把,我不餓。

曾祖父看了看說我吃。

假設這個時候曾祖父牙齒不好,吃不動,看了看小明和哥哥,說你們小,正長身體的年齡,你們吃吧。

然後小明哥倆就吃了。

接下來看看雙親委派模型的定義:

定義:

某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,

依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

解析:這裡的類加載器就是指小明一家人。

爸爸:應用程序類加載器(Application ClassLoader): 加載用戶路徑(classpath)上指定的類庫。

爺爺:擴展類加載器 (Extension ClassLoader):加載<java>\lib\ext目錄下擴展包。/<java>

曾祖父:啟動類加載器 (Bootstrap ClassLoader):加載<java>\lib目錄下核心庫。/<java>

小明和哥哥:自定義加載器(User ClassLoader)

下面看下模型

高級進階-jvm之雙親委派模型,看了發現如此簡單

注意:在這裡,雙親指的是父類和根類,對應上圖,應用程序類加載器和擴展類加載器統稱父類,啟動類加載器就是根類,對應小故事裡雙親一個是曾祖父,一個是爸爸和爺爺。

高級進階-jvm之雙親委派模型,看了發現如此簡單

二、 工作流程

1.當Application ClassLoader 收到一個類加載請求時,他首先不會自己去嘗試加載這個類,而是將這個請求委派給父類加載器Extension ClassLoader去完成。

2.當Extension ClassLoader收到一個類加載請求時,他首先也不會自己去嘗試加載這個類,而是將請求委派給父類加載器Bootstrap ClassLoader去完成。

3.如果Bootstrap ClassLoader加載失敗(在<java>\lib中未找到所需類),就會讓Extension ClassLoader嘗試加載。 /<java>

4.如果Extension ClassLoader也加載失敗,就會使用Application ClassLoader加載。

5.如果Application ClassLoader也加載失敗,就會使用自定義加載器去嘗試加載。

6.如果均加載失敗,就會拋出ClassNotFoundException異常。

高級進階-jvm之雙親委派模型,看了發現如此簡單

三、 源碼解析

protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//查找.class是否被加載過
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();
}
}
// 解析class文件,就是將符號引用替換為直接引用的過程

if (resolve) {
resolveClass(c);
}
return c;
}
}

我們再來看一下findClass方法,我們會發現這個類沒有實現,直接返回的class,說明是需要我們自己去實現的。

protected Class> findClass(String name) throws ClassNotFoundException {
return findClass(name, currentClr);
}
高級進階-jvm之雙親委派模型,看了發現如此簡單

四、 雙親委派模式的優點

採用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優先級的層次關係。

通過這種層級關可以避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。

其次是考慮到安全因素,java核心api中定義類型不會被隨意替換。

假設通過網絡傳遞一個名為java.lang.Object的類,通過雙親委託模式傳遞到啟動類加載器,而啟動類加載器在核心JavaAPI發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Object,而直接返回已加載過的Object.class,這樣便可以防止核心API庫被隨意篡改。

高級進階-jvm之雙親委派模型,看了發現如此簡單

五、 什麼情況下需要打破雙親委派模型?

1. 如果需要通過啟動類加載的第三方插件,比如JNDI不在rt.jar裡,就需要用線程上下文類加載器(Thread Context ClassLoader)使用這個線程上下文加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,實際上已經違背了雙親委派模型的一般性原則。

2. Ogsi的熱部署:為了實現熱插拔,熱部署,模塊化,意思是添加一個功能或減去一個功能不用重啟,只需要把這模塊連同類加載器一起換掉就實現了代碼的熱替換。

3. Tomcat不用重啟動態加載jsp。

高級進階-jvm之雙親委派模型,看了發現如此簡單

六、 如何打破雙親委派模型

如果不想打破雙親委派模型,就重寫ClassLoader類中的findClass()方法即可,無法被父類加載器加載的類最終會通過這個方法被加載。而如果想打破雙親委派模型則需要重寫loadClass()方法。典型的打破雙親委派模型的框架和中間件有jndi,tomcat與osgi。

---------------END-----------------

如果感覺小編寫的還可以的話可以支持一下。


分享到:


相關文章: