隨筆 - 45, 文章 - 6, 評(píng)論 - 4, 引用 - 0
          數(shù)據(jù)加載中……

          Classloader的基本概念

                  Classloader在運(yùn)行期會(huì)以父/子的層次結(jié)構(gòu)存在,每個(gè)Classloader的實(shí)例都持有其父Classloader的引用,而父Classloader并不持有子Classloader的引用,從而形成一條單向鏈,當(dāng)一個(gè)類裝載請(qǐng)求被提交到某個(gè)Classloader時(shí),其默認(rèn)的類裝載過(guò)程如下:
        1. 檢查這個(gè)類有沒(méi)有被裝載過(guò),如果已經(jīng)裝載過(guò),則直接返回;

        2. 調(diào)用父Classloader去裝載類,如果裝載成功直接返回;

        3. 調(diào)用自身的裝載類的方法,如果裝載成功直接返回;

        4. 上述所有步驟都沒(méi)有成功裝載到類,拋出ClassNotFoundException;

        5.         每一層次的Classloader都重復(fù)上述動(dòng)作。

                  簡(jiǎn)單說(shuō),當(dāng)Classloader鏈上的某一Classloader收到類裝載請(qǐng)求時(shí),會(huì)按順序向上詢問(wèn)其所有父節(jié)點(diǎn),直至最頂端(BootstrapClassLoader),任何一個(gè)節(jié)點(diǎn)成功受理了此請(qǐng)求,則返回,如果所有父節(jié)點(diǎn)都不能受理,這時(shí)候才由被請(qǐng)求的Classloader自身來(lái)裝載這個(gè)類,如果仍然不能裝載,則拋出異常。

          類裝載的方式

                  類裝載的方式主要有兩種:顯式的和隱式的。

                  顯式類裝載

                  發(fā)生在使用以下方法調(diào)用進(jìn)行裝載類的時(shí)候:

                  ClassLoader.loadClass()(使用指定的Classloader進(jìn)行裝載)

                  Class.forName()(使用當(dāng)前類的Caller Classloader進(jìn)行裝載)

                  當(dāng)調(diào)用上述方法的時(shí)候,指定的Class(以類名為參數(shù))由Classloader裝入。這兩個(gè)方法的行為有輕微的區(qū)別,Class.forName()在類裝載完成后,會(huì)對(duì)類進(jìn)行初始化,而ClassLoader.loadClass()只負(fù)責(zé)裝載類

                  隱式類裝載

                  發(fā)生在由于引用、實(shí)例化或繼承導(dǎo)致需要裝載類的時(shí)候。隱式類裝載是在幕后啟動(dòng)的,JVM會(huì)解析必要的引用并裝載類。

                  類的裝載通常組合了顯式和隱式兩種方式。例如,Classloader可能先顯式地裝載一個(gè)類,然后再隱式地裝載它引用的其它類。

          一個(gè)基本的Classloader的層次結(jié)構(gòu)
                  
                                                                      

                  上圖顯示了一個(gè)基本的Classloader的層次結(jié)構(gòu)。在給定層次上的Classloader不能引用任何層次低于它的Classloader,另外,它的子Classloader裝載的類對(duì)于其是不可見(jiàn)的。在上圖中,如果Foo.class是由ClassLoaderB裝載的,并且Foo.class依賴于Bar.class,那么Bar.class必須由ClassLoaderA或B裝載。如果Bar.class只是對(duì)ClassLoaderC和D可見(jiàn),那么將會(huì)發(fā)生ClassNotFoundException或者NoClassDefFoundError異常。

          如果Bar.class分別對(duì)于兩個(gè)平級(jí)的Classloader可見(jiàn)(例如C和D),但對(duì)于它們的父Classloader不可見(jiàn),那么當(dāng)類裝載請(qǐng)求發(fā)送到這兩個(gè)Classloader時(shí),每一個(gè)Classloader會(huì)裝載自己版本的類。ClassLoaderC裝載的Bar.class的實(shí)例將不兼容于ClassLoaderD裝載的Bar.class的實(shí)例。如果對(duì)Classloader的層次結(jié)構(gòu)不了解,試圖使用由ClassLoaderC裝載的類去造型一個(gè)ClassLoaderD裝載的Bar.class的實(shí)例,則會(huì)發(fā)生造型失?。–lassCastException)。

          基本的Classloader
                  最基本的Classloader是Bootstrap Classloader和System Classloader(也有人稱之為AppClassLoader),只要寫(xiě)過(guò)java程序,都會(huì)用到這兩個(gè)Classloader。

          • Bootstrap Classloader

            這個(gè)Classloader裝載Java虛擬機(jī)提供的基本運(yùn)行時(shí)刻類($JAVA_HOME/jre/lib),還包括放置在系統(tǒng)擴(kuò)展目錄($JAVA_HOME/jre/lib/ext)內(nèi)的JAR文件中的類。這個(gè)Classloader是java程序最頂層的Classloader,只有它沒(méi)有父Classloader。如果你將一個(gè)自己寫(xiě)的類或第三方j(luò)ar包放進(jìn)$JAVA_HOME/jre/lib/ext目錄中,那么它將被Bootstrap Classloader裝載。

          • System Classloader

            System Classloader通常負(fù)責(zé)裝載系統(tǒng)環(huán)境變量CLASSPATH中設(shè)置的類。由System Classloader裝載的類對(duì)于Apusic服務(wù)器內(nèi)部的類和部署在Apusic服務(wù)器上的J2EE應(yīng)用(通常打包成ear)都是可見(jiàn)的。%APUSIC_HOME%/lib目錄下的jar文件是Apusic應(yīng)用服務(wù)器的核心類,一般把這些jar文件都加在系統(tǒng)CLASSPATH中。另外,一些公用類也可以加在系統(tǒng)CLASSPATH中,如JDBC驅(qū)動(dòng)程序等。


          自定義Classloader
                  在編寫(xiě)應(yīng)用代碼的時(shí)候,常常有需要?jiǎng)討B(tài)加載類和資源,比如顯式的調(diào)用classLoader.loadClass(“ClassName”),雖然直接使用ClassLoader.getSystemClassLoader(),可以得到SystemlassLoader來(lái)完成這項(xiàng)任務(wù)。但是,由于System Classloader是JVM創(chuàng)建的Classloader,它的職責(zé)有限,只適合于普通的java應(yīng)用程序,在很多復(fù)雜場(chǎng)景中不能滿足需求,比如在應(yīng)用服務(wù)器中。這時(shí)候就需要自行實(shí)現(xiàn)一個(gè)Classloader的子類,實(shí)現(xiàn)特定的行為。Apusic應(yīng)用服務(wù)器中就定義了若干個(gè)特有的Classloader,負(fù)責(zé)裝載部署在Apusic中的JavaEE應(yīng)用中的類,這里并不試圖去描述如何實(shí)現(xiàn)一個(gè)自定義的Classloader.

          Caller Classloader和線程上下文Classloader
                  動(dòng)態(tài)加載資源時(shí),往往有三種Classloader可選擇:System Classloader、Caller Classloader當(dāng)前線程的上下文Classloader。System Classloader前面已經(jīng)描述過(guò)了,下面我們看看什么是Caller Classloader、當(dāng)前線程的上下文Classloader。
                  

                  Caller Classloader

                  Caller Classloader指的是當(dāng)前所在的類裝載時(shí)使用的Classloader,它可能是System Classloader,也可能是一個(gè)自定義的Classloader,這里,我們都稱之為Caller Classloader。我們可以通過(guò)getClass().getClassLoader()來(lái)得到Caller Classloader。例如,存在A類,是被AClassLoader所加載,A.class.getClassLoader()為AClassLoader的實(shí)例,它就是A.class的Caller Classloader。

                  如果在A類中使用new關(guān)鍵字,或者Class.forName(String className)和Class.getResource(String resourceName)方法,那么這時(shí)也是使用Caller Classloader來(lái)裝載類和資源。比如在A類中初始化B類:

          /**
            * A.java
          */
          ...
          public void foo() {
              B b = new B();
              b.setName("b");
          }
                  那么,B類由當(dāng)前Classloader,也就是AClassloader裝載。同樣的,修改上述的foo方法,其實(shí)現(xiàn)改為:

                  Class clazz = Class.forName("foo.B");

          最終獲取到的clazz,也是由AClassLoader所裝載。

                  那么,如何使用指定的Classloader去完成類和資源的裝載呢?或者說(shuō),當(dāng)需要去實(shí)例化一個(gè)Caller Classloader和它的父Classloader都不能裝載的類時(shí),怎么辦呢?

                  一個(gè)很典型的例子是JAXP,當(dāng)使用xerces的SAX實(shí)現(xiàn)時(shí),我們首先需要通過(guò)rt.jar中的javax.xml.parsers.SAXParserFactory.getInstance()得到xercesImpl.jar中的org.apache.xerces.jaxp.SAXParserFactoryImpl的實(shí)例。由于JAXP的框架接口的class位于JAVA_HOME/lib/rt.jar中,由Bootstrap Classloader裝載,處于Classloader層次結(jié)構(gòu)中的最頂層,而xercesImpl.jar由低層的Classloader裝載,也就是說(shuō)SAXParserFactoryImpl是在SAXParserFactory中實(shí)例化的,如前所述,使用SAXParserFactory的Caller Classloader(這里是Bootstrap Classloader)是完成不了這個(gè)任務(wù)的。

          這時(shí),我們就需要了解一下線程上下文Classloader了

                  線程上下文Classloader

          每個(gè)線程都有一個(gè)關(guān)聯(lián)的上下文Classloader。如果使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文Classloader。如果程序?qū)€程上下文Classloader沒(méi)有任何改動(dòng)的話,程序中所有的線程將都使用System Classloader作為上下文Classloader。

          當(dāng)使用Thread.currentThread().setContextClassLoader(classloader)時(shí),線程上下文Classloader就變成了指定的Classloader了。此時(shí),在本線程的任意一處地方,調(diào)用Thread.currentThread().getContextClassLoader(),都可以得到前面設(shè)置的Classloader。

          回到JAXP的例子,假設(shè)xercesImpl.jar只有AClassLoader能裝載,現(xiàn)在A.class內(nèi)部要使用JAXP,但是A.class卻不是由AClassLoader或者它的子Classloader裝載的,那么在A.class中,應(yīng)該這樣寫(xiě)才能正確得到xercesImpl的實(shí)現(xiàn):

          AClassLoader aClassLoader = new AClassLoader(parent);
          Thread.currentThread().setContextClassLoader(aClassLoader);
          SAXParserFactory factory = SAXParserFactory.getInstance();
          ...
                  JAXP這時(shí)就可以通過(guò)線程上下文Classloader裝載xercesImpl的實(shí)現(xiàn)類了,當(dāng)然,還有一個(gè)前提是在配制文件或啟動(dòng)參數(shù)中指定了使用xerces作為JAXP的實(shí)現(xiàn)。下面是JAXP中的代碼片斷:
          ClassLoader cl = Thread.currentThread().getContextClassLoader();

          Class providerClass = cl.loadClass(className);

          JVM中類的唯一性
                  JVM為每一個(gè)Classloader維護(hù)一個(gè)唯一標(biāo)識(shí)。在一個(gè)JVM里(對(duì)應(yīng)一個(gè)Java進(jìn)程),可以由不同的Classloader裝載多個(gè)同名的類(指包名和類名都完全相同,下同),為了唯一地標(biāo)識(shí)被不同Classloader裝載的類,JVM會(huì)在被裝載的類名前加上裝載該類的Classloader的標(biāo)識(shí)。

          posted on 2009-10-31 23:20 liyang 閱讀(375) 評(píng)論(0)  編輯  收藏 所屬分類: j2se


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 平凉市| 开封市| 新建县| 樟树市| 库尔勒市| 方城县| 肥城市| 墨玉县| 昌邑市| 蓝田县| 扬中市| 奇台县| 巴彦县| 怀化市| 昂仁县| 伊春市| 安达市| 芒康县| 阜康市| 康定县| 湖南省| 岳阳市| 平邑县| 阳东县| 将乐县| 二连浩特市| 浑源县| 横山县| 绥阳县| 辉县市| 西盟| 渝中区| 泗水县| 克拉玛依市| 滦南县| 辰溪县| 太白县| 宁夏| 汨罗市| 祥云县| 洛扎县|