1. 程式人生 > >Java雙親委派模型

Java雙親委派模型

static AS eight 這樣的 found 類加載器 can 兩種 RR

Java雙親委派模型詳解

我們在了解雙親委派模型之前,不得不先了解一下什麽是類加載器。虛擬機設計團隊之初是希望類加載過程“通過一個類的全限定名來獲取描述該類的二進制字節流”這個動作能放到虛擬機外部實現,以便於讓程序自己決定如何獲取該類,實現這個動作的代碼的工具成為類加載器。

可能很多人覺得類加載器,顧名思義,就是個加載類的嘛,有啥大不了的,但是類加載這個過程是很嚴格的,對於任意一個類,我們都需要由加載他的類加載器和類的本身來決定該類在虛擬機之中的唯一性。什麽意思呢??就是說我們的虛擬機要比較兩個類是否相等,那前提條件是就是這兩個類必須是在同一個類加載器加載的,如果兩個類都不是由同一個加載器加載的,那麽這倆類就一定不相等,所以就沒有比較的意義!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name)
throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name);
} byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest); } }

  就比如上面這段代碼,代碼運行結果很令人失望,雖然打印出的類路徑是相同的,但是比較後的結果卻是false,這是為啥啊?好氣啊,明明類路徑一樣,但是結果卻不同?

這是因為,我們自己實現了屬於我們自己的類加載器,我們選擇了我們自己的加載路徑去加載該類,而另一個同類路徑名的類卻是由另一個加載器(應用程序類加載器)加載的,只要不是同一個類加載器加載的類,一定不是同一個類!!

雙親委派模型

從java虛擬機角度來講,只存在兩種不同的類加載器:

(1)一種是啟動類加載器,由C++語言實現的,屬於虛擬機的一部分;

(2)一種是所有的其他類加載器,這些都是由Java實現的,獨立於虛擬機外部,繼承自java.lang.ClassLoader;

但是從開發人員角度來講,應該分的再細一些,絕大部分程序都使用到了以下三種系統提供的類加載器:

(1)啟動類加載器,該加載器是C++實現的,它負責加載存放於<JAVA_HOME>\lib目錄下的類,它是僅僅按照文件的名字來識別的,名字不符合的類就算放到該目錄下,也是毫無卵用的.....

(2)擴展類加載器,它是負責加載<JAVA_HOME>\lib\Ext目錄下的;

(3)應用程序類加載器,這個類也被稱為系統類加載器,它是負責用戶類路徑classpath上指定的類庫,開發者可以直接使用這個加載器;

應用程序都是由這三種加載器相互配合進行加載的,有必要的話,還可以實現屬於自己的類加載器,這幾種加載器關系如圖:

技術分享圖片

這種層次結構我們就稱之為雙親委派模型,可以很直觀的看出除了頂層的啟動類加載器外,其他的都有屬於自己的父類加載器。但是我們在這裏不要混淆一個概念,就是繼承(Inheritance),這個結構圖並不是繼承關系而是通過組合的方式來實現向上委托的.......

雙親委派的工作流程就是:如果一個類加載器收到了類加載的請求,它是不會自己立馬親自動手去加載的(比較懶,哈哈!),而是把該請求委托給父類,每一層都是如此,到了頂層後,這時就無法再向上傳遞了,所有的請求都集中到了啟動類加載器,當父類反饋自己無法滿足這個請求時,這時就會再把請求一層層向下傳遞。

這樣的好處是啥??相信大家看這種層次結構應該很清晰,但是這有什麽意義嗎?比如java.lang.Object,他是存在rt.jar裏的,不論哪種加載器,是系統自帶的也好還是我們自己實現的也好,都會把請求一層層的往上委托,直到啟動類加載器,而啟動類加載器一看,自己是有這個類的,所以加載,因此Object在程序的各個類加載器的加載下永遠都是同一個類。反之,沒有雙親委派模型,任由各個類加載器自己去加載的話,比如我們開發者自己寫了Object類,包名也是java.lang,那麽系統中就會出現各種各樣的Object,每一個層級的類加載器都加載了自己具有個性的Object,那麽作為程序中這麽基礎這麽重要的Object,他的唯一性得不到保證,應用程序就會雜亂不堪。

雙親委派模型的作用想必到這裏很多人應該清楚了,覺得:“哇!這個模型還真的是很強大呢...”。他的實現也是非常簡單的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{ //check the class has been loaded or not Class c = findLoadedClass(name); if(c == null){ try{ if(parent != null){ c = parent.loadClass(name,false); }else{ c = findBootstrapClassOrNull(name); } }catch(ClassNotFoundException e){ //if throws the exception ,the father can not complete the load } if(c == null){ c = findClass(name); } } if(resolve){ resolveClass(c); } return c; }

從圖中的代碼,我們大致可以看出這個委托機制是如何實現的,當一個加載器收到請求後,首先會判斷一下當前這個類是否已經被加載過,如果沒有被加載的話,開始委托父類加載器了(就是這麽懶,哈哈),如果沒有父類的話,就默認使用啟動類加載器。如果拋異常了,就代表當前類加載器的父類無法加載,滿足不了請求,那麽此時只能自己親自出馬了!!所以什麽事還是自己來做的靠譜啊哈哈。

總結

當然,這種模型一直都不是強制性的,而是推薦我們這麽做的,往年就出現過打破該機制的事件,典型的例子就是JNDI服務,他的代碼是交給啟動類加載器去實現的,但是當JNDI要對資源進行集中化管理時,他需要調用其他公司實現並部署在應用程序的classpath下的JNDI接口,因為這些代碼是需要我們開發者自己來實現的,這時啟動類加載器是無法識別這些類的,於是乎出現了一種線程上下文加載器,JNDI服務可以調用該加載器去加載所需要的代碼,就是通過父類加載器去請求子類加載器來實現的,這已經很明顯的違背了雙親委派模型。

Java雙親委派模型