“雙親委派模型”的無奈


今天的主人公是 “雙親委派模型” ,簡稱 “雙模”。

那“雙模”是誰?

從中文字面意思來理解,這個“雙模”就是一種模版,類似於一種規則或者制度。

那為什么要叫做 “雙親委派模型” 呢?其實我也不知道。

從英文名字上看(parent-delegation model),可能是將英文的 parent 直接翻譯過來是 “父母雙親“ 的意思了吧,故而叫了“雙親委派模型”,純屬瞎猜,沒有任何文獻指導!^_^~~

具體的講,“雙模”是類加載器之間的一種固定的層次關系。關系如下圖:

 

 

 

那么問題就來了,類加載器又是什么?

什么是類加載器

虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作放到Java虛擬機外部去實現,
以便讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類加載器”。

那什么又是類加載呢?這個問題就不在此贅述了,書上有寫,哈哈哈。

類與類加載器:

"對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。" 
一個類必須由加載它的類加載器和這個類本來一起來確定其在java虛擬機中的唯一性。
反過來說,由於類本身是固定的,即來源於同一個Class文件,也就是說確定這個類的唯一性是由加載這個類的類加載器來決定的,不同的類加載器加載出來的類的實例是不相同的。

“雙模”從哪兒來?

“雙親委派模型” 當然是被Java設計師們設計出來的呀。

雙親委派模型的原理

雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器。
這里類加載器之間的父子關系一般不會以繼承(Inheritance)的關系來實現,而是都使用組合(Composition)關系來復用父加載器的代碼。
雙親委派模型的工作過程是:
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,
因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

類加載器分類

"從Java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分;
另一種就是所有其他的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,並且全都繼承自抽象類java.lang.ClassLoader。"

 從虛擬機角度來看,類加載器只有2種:

第1種是 啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++實現,是虛擬機自身的一部分。

第2中是 其他類加載器,這些類加載器由Java語言實現,獨立於虛擬機外部,並且全部都繼承自抽象類java.lang.ClassLoader。

從Java開發人員的角度來看,類加載器有以下3(+1)種系統提供的類加載器。

第1種是 啟動類加載器(Bootstrap ClassLoader)

負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。
啟動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可。

第2種是 擴展類加載器(Extension ClassLoader):

這個加載器由sun.misc.Launcher $ExtClassLoader實現,它負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。

第3種是 應用程序類加載器(Application ClassLoader):

這個類加載器由sun.misc.Launcher $App-ClassLoader實現。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類加載器。
它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

第4種是 自定義的類加載器:如果有必要。

“雙模”要到哪兒去?

雙親委派模型的意義?

"使用雙親委派模型來組織類加載器之間的關系,有一個顯而易見的好處就是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關系。
" 雙親委派模型用來組織類加載器之間的關系,形成了一種帶有優先級的層級關系,確保了被加載出來的類的唯一性,維護了系統的穩定。

可以總結為:為了維護Java程序系統的和平穩定發展。

”雙模“也有無奈

無奈之一:“父債子償”

由於雙親委派模型在JDK 1.2之后才被引入,而類加載器和抽象類java.lang.ClassLoader早則在JDK 1.0時代就已經存在,面對已經存在的用戶自定義類加載器的實現代碼,
Java設計者引入雙親委派模型時不得不做出一些妥協。為了向前兼容,JDK 1.2之后的java.lang.ClassLoader添加了一個新的protected方法findClass(),
在此之前,用戶去繼承java.lang.ClassLoader的唯一目的就是為了重寫loadClass()方法,因為虛擬機在進行類加載的時候會調用加載器的私有方法loadClassInternal(),
而這個方法的唯一邏輯就是去調用自己的loadClass()。

JDK 1.2之后已不提倡用戶再去覆蓋loadClass()方法,而應當把自己的類加載邏輯寫到findClass()方法中,

在loadClass()方法的邏輯里如果父類加載失敗,則會調用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是符合雙親委派規則的。

無奈之二:天生不足的無奈。

由這個模型自身的缺陷所導致的,雙親委派很好地解決了各個類加載器的基礎類的統一問題(越基礎的類由越上層的加載器進行加載),基礎類之所以稱為“基礎”,
是因為它們總是作為被用戶代碼調用的API,但世事往往沒有絕對的完美,如果基礎類又要調用回用戶的代碼,那該怎么辦?
為了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(Thread Context ClassLoader)。
這個類加載器可以通過java.lang.Thread類的setContextClassLoaser()方法進行設置,如果創建線程時還未設置,它將會從父線程中繼承一個,
如果在應用程序的全局范圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。

有了“線程上下文類加載器”,就相當於手握外掛,也就可以做一些“舞弊”的事情了。

JNDI服務使用這個線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,

這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,實際上已經違背了雙親委派模型的一般性原則,

但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

無奈之三:這個世界唯一不變的地方就是一直在變。

由於用戶對程序動態性的追求而導致的,這里所說的“動態性”指的是當前一些非常“熱門”的名詞:代碼熱替換(HotSwap)、模塊熱部署(Hot Deployment)等,
說白了就是希望應用程序能像我們的計算機外設那樣,接上鼠標、U盤,不用重啟機器就能立即使用,鼠標有問題或要升級就換個鼠標,不用停機也不用重啟。
對於個人計算機來說,重啟一次其實沒有什么大不了的,但對於一些生產系統來說,關機重啟一次可能就要被列為生產事故,這種情況下熱部署就對軟件開發者,
尤其是企業級軟件開發者具有很大的吸引力。

在OSGi環境下,類加載器不再是雙親委派模型中的樹狀結構,而是進一步發展為更加復雜的網狀結構,

當收到類加載請求時,OSGi將按照下面的順序進行類搜索:

1)將以java.*開頭的類委派給父類加載器加載。

2)否則,將委派列表名單內的類委派給父類加載器加載。

3)否則,將Import列表中的類委派給Export這個類的Bundle的類加載器加載。

4)否則,查找當前Bundle的ClassPath,使用自己的類加載器加載。

5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類加載器加載。

6)否則,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載。

7)否則,類查找失敗。 上面的查找順序中只有開頭兩點仍然符合雙親委派規則,其余的類查找都是在平級的類加載器中進行的。

 

說在最后:

以上內容說的是HotSpot。^_^


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM