Live a simple life

          沉默(zhu_xing@live.cn)
          隨筆 - 48, 文章 - 0, 評(píng)論 - 132, 引用 - 0
          數(shù)據(jù)加載中……

          【原創(chuàng)】Java類加載原理解析

           

          Java類加載原理解析                                        

          1       基本信息

          摘要:

          每個(gè)java開發(fā)人員對(duì)java.lang.ClassNotFoundExcetpion這個(gè)異??隙ǘ疾荒吧?,這背后就涉及到了java技術(shù)體系中的類加載。Java的類加載機(jī)制是java技術(shù)體系中比較核心的部分,雖然和大部分開發(fā)人員直接打交道不多,但是對(duì)其背后的機(jī)理有一定理解有助于排查程序中出現(xiàn)的類加載失敗等技術(shù)問題,對(duì)理解java虛擬機(jī)的連接模型和java語言的動(dòng)態(tài)性都有很大幫助。

          由于關(guān)于java類加載的內(nèi)容較多,所以打算分三篇文章簡(jiǎn)述一下:

          第一篇:java類加載原理解析

          第二篇:插件環(huán)境下類加載原理解析

          第三篇:線程上下文類加載器

          分類開發(fā)技術(shù)->J2EE

          標(biāo)簽:Java類加載 類加載器 雙親委派機(jī)制 自定義類加載器

          作者:朱興 創(chuàng)建于2007-6-22          MSNzhu_xing@live.cn 

          2       Java虛擬機(jī)類加載器結(jié)構(gòu)簡(jiǎn)述

          2.1    JVM三種預(yù)定義類型類加載器

          我們首先看一下JVM預(yù)定義的三種類型類加載器,當(dāng)一個(gè) JVM 啟動(dòng)的時(shí)候,Java 缺省開始使用如下三種類型類裝入器:

          啟動(dòng)(Bootstrap)類加載器:引導(dǎo)類裝入器是用本地代碼實(shí)現(xiàn)的類裝入器,它負(fù)責(zé)將 <Java_Runtime_Home>/lib 下面的類庫加載到內(nèi)存中。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動(dòng)類加載器的引用,所以不允許直接通過引用進(jìn)行操作。

          標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器:擴(kuò)展類加載器是由 Sun ExtClassLoadersun.misc.Launcher$ExtClassLoader 實(shí)現(xiàn)的。它負(fù)責(zé)將 < Java_Runtime_Home >/lib/ext 或者由系統(tǒng)變量 java.ext.dir 指定位置中的類庫加載到內(nèi)存中。開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。

          系統(tǒng)(System)類加載器:系統(tǒng)類加載器是由 Sun AppClassLoadersun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器。

          除了以上列舉的三種類加載器,還有一種比較特殊的類型就是線程上下文類加載器,這個(gè)將在后面單獨(dú)介紹。

          2.2    類加載雙親委派機(jī)制介紹和分析

                 在這里,需要著重說明的是,JVM在加載類時(shí)默認(rèn)采用的是雙親委派機(jī)制。通俗的講,就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。關(guān)于虛擬機(jī)默認(rèn)的雙親委派機(jī)制,我們可以從系統(tǒng)類加載器和標(biāo)準(zhǔn)擴(kuò)展類加載器為例作簡(jiǎn)單分析。

                           
                           圖一 標(biāo)準(zhǔn)擴(kuò)展類加載器繼承層次圖

                             
                  圖二 系統(tǒng)類加載器繼承層次圖

                 通過圖一和圖二我們可以看出,類加載器均是繼承自java.lang.ClassLoader抽象類。我們下面我們就看簡(jiǎn)要介紹一下java.lang.ClassLoader中幾個(gè)最重要的方法:

          //加載指定名稱(包括包名)的二進(jìn)制類型,供用戶調(diào)用的接口

          public Class<?> loadClass(String name) throws ClassNotFoundException{//…}

          //加載指定名稱(包括包名)的二進(jìn)制類型,同時(shí)指定是否解析(但是,這里的resolve參數(shù)不一定真正能達(dá)到解析的效果~_~),供繼承用

          protectedsynchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//…}

          //findClass方法一般被loadClass方法調(diào)用去加載指定名稱類,供繼承用

          protected Class<?> findClass(String name) throws ClassNotFoundException {//…}

          //定義類型,一般在findClass方法中讀取到對(duì)應(yīng)字節(jié)碼后調(diào)用,可以看出不可繼承(說明:JVM已經(jīng)實(shí)現(xiàn)了對(duì)應(yīng)的具體功能,解析對(duì)應(yīng)的字節(jié)碼,產(chǎn)生對(duì)應(yīng)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)放置到方法區(qū),所以無需覆寫,直接調(diào)用就可以了)

          protected final Class<?> defineClass(String name, byte[] b, int off, int len)

          throws ClassFormatError{//…}

                 通過進(jìn)一步分析標(biāo)準(zhǔn)擴(kuò)展類加載器(sun.misc.Launcher$ExtClassLoader)和系統(tǒng)類加載器(sun.misc.Launcher$AppClassLoader)的代碼以及其公共父類(java.net.URLClassLoaderjava.security.SecureClassLoader)的代碼可以看出,都沒有覆寫java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則---loadClass)方法。既然這樣,我們就可以通過分析java.lang.ClassLoader中的loadClassString name)方法的代碼就可以分析出虛擬機(jī)默認(rèn)采用的雙親委派機(jī)制到底是什么模樣:

          public Class<?> loadClass(String name)throws ClassNotFoundException {

                  return loadClass(name, false);

          }

          protectedsynchronized Class<?> loadClass(String name, boolean resolve)

                      throws ClassNotFoundException {

                  // 首先判斷該類型是否已經(jīng)被加載

                  Class c = findLoadedClass(name);

                  if (c == null) {

                      //如果沒有被加載,就委托給父類加載或者委派給啟動(dòng)類加載器加載

                      try {

                          if (parent != null) {

          //如果存在父類加載器,就委派給父類加載器加載

                              c = parent.loadClass(name, false);

                          } else {

          //如果不存在父類加載器,就檢查是否是由啟動(dòng)類加載器加載的類,通過調(diào)用本地方法native Class findBootstrapClass(String name)

                              c = findBootstrapClass0(name);

                          }

                      } catch (ClassNotFoundException e) {

                  // 如果父類加載器和啟動(dòng)類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能

                          c = findClass(name);

                      }

                  }

                  if (resolve) {

                      resolveClass(c);

                  }

                  return c;

              }

              通過上面的代碼分析,我們可以對(duì)JVM采用的雙親委派類加載機(jī)制有了更感性的認(rèn)識(shí),下面我們就接著分析一下啟動(dòng)類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器三者之間的關(guān)系。可能大家已經(jīng)從各種資料上面看到了如下類似的一幅圖片:


                              
                                       圖三 類加載器默認(rèn)委派關(guān)系圖

          上面圖片給人的直觀印象是系統(tǒng)類加載器的父類加載器是標(biāo)準(zhǔn)擴(kuò)展類加載器,標(biāo)準(zhǔn)擴(kuò)展類加載器的父類加載器是啟動(dòng)類加載器,下面我們就用代碼具體測(cè)試一下:

          示例代碼:

          public static void main(String[] args) {
             
          try {
               System.out.println(ClassLoader.getSystemClassLoader());
               System.out.println(ClassLoader.getSystemClassLoader().getParent();
               System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
             } 
          catch (Exception e) {
                 e.printStackTrace();
             }
          }


          說明:通過java.lang.ClassLoader.getSystemClassLoader()可以直接獲取到系統(tǒng)類加載器。

          代碼輸出如下:

          sun.misc.Launcher$AppClassLoader@197d257

          sun.misc.Launcher$ExtClassLoader@7259da

          null

              通過以上的代碼輸出,我們可以判定系統(tǒng)類加載器的父加載器是標(biāo)準(zhǔn)擴(kuò)展類加載器,但是我們?cè)噲D獲取標(biāo)準(zhǔn)擴(kuò)展類加載器的父類加載器時(shí)確得到了null,就是說標(biāo)準(zhǔn)擴(kuò)展類加載器本身強(qiáng)制設(shè)定父類加載器為null。我們還是借助于代碼分析一下:

               我們首先看一下java.lang.ClassLoader抽象類中默認(rèn)實(shí)現(xiàn)的兩個(gè)構(gòu)造函數(shù):

              protected ClassLoader() {

                  SecurityManager security = System.getSecurityManager();

                  if (security != null) {

                      security.checkCreateClassLoader();

                  }

                  //默認(rèn)將父類加載器設(shè)置為系統(tǒng)類加載器,getSystemClassLoader()獲取系統(tǒng)類加載器

                  this.parent = getSystemClassLoader();

                  initialized = true;

              }

              protected ClassLoader(ClassLoader parent) {

                  SecurityManager security = System.getSecurityManager();

                  if (security != null) {

                      security.checkCreateClassLoader();

                  }

                  //強(qiáng)制設(shè)置父類加載器

                  this.parent = parent;

                  initialized = true;

              }

              我們?cè)倏匆幌?/span>ClassLoader抽象類中parent成員的聲明:

                 // The parent class loader for delegation

          private ClassLoader parent;

          聲明為私有變量的同時(shí)并沒有對(duì)外提供可供派生類訪問的public或者protected設(shè)置器接口(對(duì)應(yīng)的setter方法),結(jié)合前面的測(cè)試代碼的輸出,我們可以推斷出:

          1.              系統(tǒng)類加載器(AppClassLoader)調(diào)用ClassLoader(ClassLoader parent)構(gòu)造函數(shù)將父類加載器設(shè)置為標(biāo)準(zhǔn)擴(kuò)展類加載器(ExtClassLoader)。(因?yàn)槿绻粡?qiáng)制設(shè)置,默認(rèn)會(huì)通過調(diào)用getSystemClassLoader()方法獲取并設(shè)置成系統(tǒng)類加載器,這顯然和測(cè)試輸出結(jié)果不符。)

          2.               擴(kuò)展類加載器(ExtClassLoader調(diào)用ClassLoader(ClassLoader parent)構(gòu)造函數(shù)將父類加載器設(shè)置為null。(因?yàn)槿绻粡?qiáng)制設(shè)置,默認(rèn)會(huì)通過調(diào)用getSystemClassLoader()方法獲取并設(shè)置成系統(tǒng)類加載器,這顯然和測(cè)試輸出結(jié)果不符。)

               現(xiàn)在我們可能會(huì)有這樣的疑問:擴(kuò)展類加載器(ExtClassLoader)的父類加載器被強(qiáng)制設(shè)置為null了,那么擴(kuò)展類加載器為什么還能將加載任務(wù)委派給啟動(dòng)類加載器呢?

                        

                   圖四 標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器成員大綱視圖

                            
                     
                     圖五
          擴(kuò)展類加載器和系統(tǒng)類加載器公共父類成員大綱視圖

              通過圖四和圖五可以看出,標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器及其父類(java.net.URLClassLoaderjava.security.SecureClassLoader都沒有覆寫java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則---loadClass)方法。有關(guān)java.lang.ClassLoader中默認(rèn)的加載委派規(guī)則前面已經(jīng)分析過,如果父加載器為null,則會(huì)調(diào)用本地方法進(jìn)行啟動(dòng)類加載嘗試。所以,圖三中,啟動(dòng)類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器之間的委派關(guān)系事實(shí)上是仍就成立的。(在后面的用戶自定義類加載器部分,還會(huì)做更深入的分析)。

          2.3    類加載雙親委派示例

          以上已經(jīng)簡(jiǎn)要介紹了虛擬機(jī)默認(rèn)使用的啟動(dòng)類加載器、標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器,并以三者為例結(jié)合JDK代碼對(duì)JVM默認(rèn)使用的雙親委派類加載機(jī)制做了分析。下面我們就來看一個(gè)綜合的例子。首先在eclipse中建立一個(gè)簡(jiǎn)單的java應(yīng)用工程,然后寫一個(gè)簡(jiǎn)單的JavaBean如下:

          package classloader.test.bean;

              publicclass TestBean {

                  public TestBean() {}

          }

          在現(xiàn)有當(dāng)前工程中另外建立一測(cè)試類(ClassLoaderTest.java)內(nèi)容如下:

          測(cè)試一:

          publicclass ClassLoaderTest {

              publicstaticvoid main(String[] args) {

                  try {

                      //查看當(dāng)前系統(tǒng)類路徑中包含的路徑條目

                      System.out.println(System.getProperty("java.class.path"));

          //調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載TestBean

          Class typeLoaded = Class.forName("classloader.test.bean.TestBean");

          //查看被加載的TestBean類型是被那個(gè)類加載器加載的

                      System.out.println(typeLoaded.getClassLoader());

                  } catch (Exception e) {

                      e.printStackTrace();

                  }

              }

          }

          對(duì)應(yīng)的輸出如下:

          D:"DEMO"dev"Study"ClassLoaderTest"bin

          sun.misc.Launcher$AppClassLoader@197d257

          (說明:當(dāng)前類路徑默認(rèn)的含有的一個(gè)條目就是工程的輸出目錄)

          測(cè)試二:

          將當(dāng)前工程輸出目錄下的…/classloader/test/bean/TestBean.class打包進(jìn)test.jar剪貼< Java_Runtime_Home >/lib/ext目錄下(現(xiàn)在工程輸出目錄下和JRE擴(kuò)展目錄下都有待加載類型的class文件)。再運(yùn)行測(cè)試一測(cè)試代碼,結(jié)果如下:

          D:"DEMO"dev"Study"ClassLoaderTest"bin

          sun.misc.Launcher$ExtClassLoader@7259da

          對(duì)比測(cè)試一和測(cè)試二,我們明顯可以驗(yàn)證前面說的雙親委派機(jī)制,系統(tǒng)類加載器在接到加載classloader.test.bean.TestBean類型的請(qǐng)求時(shí),首先將請(qǐng)求委派給父類加載器(標(biāo)準(zhǔn)擴(kuò)展類加載器),標(biāo)準(zhǔn)擴(kuò)展類加載器搶先完成了加載請(qǐng)求。

              測(cè)試三:

          test.jar拷貝一份到< Java_Runtime_Home >/lib下,運(yùn)行測(cè)試代碼,輸出如下:

          D:"DEMO"dev"Study"ClassLoaderTest"bin

          sun.misc.Launcher$ExtClassLoader@7259da

             測(cè)試三和測(cè)試二輸出結(jié)果一致。那就是說,放置到< Java_Runtime_Home >/lib目錄下的TestBean對(duì)應(yīng)的class字節(jié)碼并沒有被加載,這其實(shí)和前面講的雙親委派機(jī)制并不矛盾。虛擬機(jī)出于安全等因素考慮,不會(huì)加載< Java_Runtime_Home >/lib存在的陌生類開發(fā)者通過將要加載的非JDK自身的類放置到此目錄下期待啟動(dòng)類加載器加載是不可能的。做個(gè)進(jìn)一步驗(yàn)證,刪除< Java_Runtime_Home >/lib/ext目錄下和工程輸出目錄下的TestBean對(duì)應(yīng)的class文件,然后再運(yùn)行測(cè)試代碼,則將會(huì)有ClassNotFoundException異常拋出。有關(guān)這個(gè)問題,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中設(shè)置相應(yīng)斷點(diǎn)運(yùn)行測(cè)試三進(jìn)行調(diào)試,會(huì)發(fā)現(xiàn)findBootstrapClass0()會(huì)拋出異常,然后在下面的findClass方法中被加載,當(dāng)前運(yùn)行的類加載器正是擴(kuò)展類加載器(sun.misc.Launcher$ExtClassLoader),這一點(diǎn)可以通過JDT中變量視圖查看驗(yàn)證。

          3       java程序動(dòng)態(tài)擴(kuò)展方式

          Java連接模型允許用戶運(yùn)行時(shí)擴(kuò)展引用程序,既可以通過當(dāng)前虛擬機(jī)中預(yù)定義的加載器加載編譯時(shí)已知的類或者接口,又允許用戶自行定義類裝載器,在運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展用戶的程序。通過用戶自定義的類裝載器,你的程序可以裝載在編譯時(shí)并不知道或者尚未存在的類或者接口,并動(dòng)態(tài)連接它們并進(jìn)行有選擇的解析。

                 運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展java應(yīng)用程序有如下兩個(gè)途徑:

          3.1    調(diào)用java.lang.Class.forName(…)

          這個(gè)方法其實(shí)在前面已經(jīng)討論過,在后面的問題2解答中說明了該方法調(diào)用會(huì)觸發(fā)那個(gè)類加載器開始加載任務(wù)。這里需要說明的是多參數(shù)版本的forName(…)方法:

          public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

          這里的initialize參數(shù)是很重要的,可以覺得被加載同時(shí)是否完成初始化的工作(說明: 單參數(shù)版本的forName方法默認(rèn)是不完成初始化的).有些場(chǎng)景下,需要將initialize設(shè)置為true來強(qiáng)制加載同時(shí)完成初始化,例如典型的就是利用DriverManager進(jìn)行JDBC驅(qū)動(dòng)程序類注冊(cè)的問題,因?yàn)槊恳粋€(gè)JDBC驅(qū)動(dòng)程序類的靜態(tài)初始化方法都用DriverManager注冊(cè)驅(qū)動(dòng)程序,這樣才能被應(yīng)用程序使用,這就要求驅(qū)動(dòng)程序類必須被初始化,而不單單被加載.

          3.2    用戶自定義類加載器

          通過前面的分析,我們可以看出,除了和本地實(shí)現(xiàn)密切相關(guān)的啟動(dòng)類加載器之外,包括標(biāo)準(zhǔn)擴(kuò)展類加載器和系統(tǒng)類加載器在內(nèi)的所有其他類加載器我們都可以當(dāng)做自定義類加載器來對(duì)待,唯一區(qū)別是是否被虛擬機(jī)默認(rèn)使用。前面的內(nèi)容中已經(jīng)對(duì)java.lang.ClassLoader抽象類中的幾個(gè)重要的方法做了介紹,這里就簡(jiǎn)要敘述一下一般用戶自定義類加載器的工作流程吧(可以結(jié)合后面問題解答一起看):

          1、首先檢查請(qǐng)求的類型是否已經(jīng)被這個(gè)類裝載器裝載到命名空間中了,如果已經(jīng)裝載,直接返回;否則轉(zhuǎn)入步驟2

          2、委派類加載請(qǐng)求給父類加載器(更準(zhǔn)確的說應(yīng)該是雙親類加載器,真?zhèn)€虛擬機(jī)中各種類加載器最終會(huì)呈現(xiàn)樹狀結(jié)構(gòu)),如果父類加載器能夠完成,則返回父類加載器加載的Class實(shí)例;否則轉(zhuǎn)入步驟3

          3、調(diào)用本類加載器的findClass)方法,試圖獲取對(duì)應(yīng)的字節(jié)碼,如果獲取的到,則調(diào)用defineClass)導(dǎo)入類型到方法區(qū);如果獲取不到對(duì)應(yīng)的字節(jié)碼或者其他原因失敗,返回異常給loadClass), loadClass)轉(zhuǎn)拋異常,終止加載過程(注意:這里的異常種類不止一種)。

                 (說明:這里說的自定義類加載器是指JDK 1.2以后版本的寫法,即不覆寫改變java.lang.loadClass(…)已有委派邏輯情況下)

          4       常見問題分析:

          4.1    由不同的類加載器加載的指定類型還是相同的類型嗎?

          Java中,一個(gè)類用其完全匹配類名(fully qualified class name)作為標(biāo)識(shí),這里指的完全匹配類名包括包名和類名。但在JVM中一個(gè)類用其全名和一個(gè)加載類ClassLoader的實(shí)例作為唯一標(biāo)識(shí),不同類加載器加載的類將被置于不同的命名空間.我們可以用兩個(gè)自定義類加載器去加載某自定義類型(注意,不要將自定義類型的字節(jié)碼放置到系統(tǒng)路徑或者擴(kuò)展路徑中,否則會(huì)被系統(tǒng)類加載器或擴(kuò)展類加載器搶先加載),然后用獲取到的兩個(gè)Class實(shí)例進(jìn)行java.lang.Object.equals)判斷,將會(huì)得到不相等的結(jié)果。這個(gè)大家可以寫兩個(gè)自定義的類加載器去加載相同的自定義類型,然后做個(gè)判斷;同時(shí),可以測(cè)試加載java.*類型,然后再對(duì)比測(cè)試一下測(cè)試結(jié)果。

          4.2    在代碼中直接調(diào)用Class.forNameString name)方法,到底會(huì)觸發(fā)那個(gè)類加載器進(jìn)行類加載行為?

          Class.forName(String name)默認(rèn)會(huì)使用調(diào)用類的類加載器來進(jìn)行類加載。我們直接來分析一下對(duì)應(yīng)的jdk的代碼:

          //java.lang.Class.java

                 publicstatic Class<?> forName(String className)throws ClassNotFoundException {

          return forName0(className, true, ClassLoader.getCallerClassLoader());

          }

          //java.lang.ClassLoader.java

          // Returns the invoker's class loader, or null if none.

          static ClassLoader getCallerClassLoader() {

                        // 獲取調(diào)用類(caller)的類型

                  Class caller = Reflection.getCallerClass(3);

                        // This can be null if the VM is requesting it

                  if (caller == null) {

                      returnnull;

                  }

                  // 調(diào)用java.lang.Class中本地方法獲取加載該調(diào)用類(caller)的ClassLoader

                  return caller.getClassLoader0();

          }

          //java.lang.Class.java

          //虛擬機(jī)本地實(shí)現(xiàn),獲取當(dāng)前類的類加載器,前面介紹的ClassgetClassLoader()也使用此方法

          native ClassLoader getClassLoader0();

          4.3    在編寫自定義類加載器時(shí),如果沒有設(shè)定父加載器,那么父加載器是?

          前面講過,在不指定父類加載器的情況下,默認(rèn)采用系統(tǒng)類加載器??赡苡腥擞X得不明白,現(xiàn)在我們來看一下JDK對(duì)應(yīng)的代碼實(shí)現(xiàn)。眾所周知,我們編寫自定義的類加載器直接或者間接繼承自java.lang.ClassLoader抽象類,對(duì)應(yīng)的無參默認(rèn)構(gòu)造函數(shù)實(shí)現(xiàn)如下:

          //摘自java.lang.ClassLoader.java

          protected ClassLoader() {

                     SecurityManager security = System.getSecurityManager();

                     if (security != null) {

                         security.checkCreateClassLoader();

                     }

                     this.parent = getSystemClassLoader();

                     initialized = true;

          }

          我們?cè)賮砜匆幌聦?duì)應(yīng)的getSystemClassLoader()方法的實(shí)現(xiàn):

          privatestaticsynchronizedvoid initSystemClassLoader() {

                     //...

                     sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

                     scl = l.getClassLoader();

                     //...

          }

          我們可以寫簡(jiǎn)單的測(cè)試代碼來測(cè)試一下:

          System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

          本機(jī)對(duì)應(yīng)輸出如下:

          sun.misc.Launcher$AppClassLoader@197d257

          所以,我們現(xiàn)在可以相信當(dāng)自定義類加載器沒有指定父類加載器的情況下,默認(rèn)的父類加載器即為系統(tǒng)類加載器。同時(shí),我們可以得出如下結(jié)論:

          即時(shí)用戶自定義類加載器不指定父類加載器,那么,同樣可以加載如下三個(gè)地方的類:

          1.    <Java_Runtime_Home>/lib下的類

          2.    < Java_Runtime_Home >/lib/ext下或者由系統(tǒng)變量java.ext.dir指定位置中的類

          3.    當(dāng)前工程類路徑下或者由系統(tǒng)變量java.class.path指定位置中的類

          4.4    在編寫自定義類加載器時(shí),如果將父類加載器強(qiáng)制設(shè)置為null,那么會(huì)有什么影響?如果自定義的類加載器不能加載指定類,就肯定會(huì)加載失敗嗎?

          JVM規(guī)范中規(guī)定如果用戶自定義的類加載器將父類加載器強(qiáng)制設(shè)置為null,那么會(huì)自動(dòng)將啟動(dòng)類加載器設(shè)置為當(dāng)前用戶自定義類加載器的父類加載器(這個(gè)問題前面已經(jīng)分析過了)同時(shí),我們可以得出如下結(jié)論:

          即時(shí)用戶自定義類加載器不指定父類加載器,那么,同樣可以加載到<Java_Runtime_Home>/lib下的類,但此時(shí)就不能夠加載<Java_Runtime_Home>/lib/ext目錄下的類了。

              說明:?jiǎn)栴}3和問題4的推斷結(jié)論是基于用戶自定義的類加載器本身延續(xù)了java.lang.ClassLoader.loadClass)默認(rèn)委派邏輯,如果用戶對(duì)這一默認(rèn)委派邏輯進(jìn)行了改變,以上推斷結(jié)論就不一定成立了,詳見問題5

          4.5    編寫自定義類加載器時(shí),一般有哪些注意點(diǎn)?

          1.      一般盡量不要覆寫已有的loadClass)方法中的委派邏輯

          一般在JDK 1.2之前的版本才這樣做,而且事實(shí)證明,這樣做極有可能引起系統(tǒng)默認(rèn)的類加載器不能正常工作。JVM規(guī)范和JDK文檔中(1.2或者以后版本中),都沒有建議用戶覆寫loadClass(…)方法,相比而言,明確提示開發(fā)者在開發(fā)自定義的類加載器時(shí)覆寫findClass(…)邏輯。舉一個(gè)例子來驗(yàn)證該問題:

          //用戶自定義類加載器WrongClassLoader.Java(覆寫loadClass邏輯)

          publicclassWrongClassLoaderextends ClassLoader {

                  public Class<?> loadClass(String name) throws ClassNotFoundException {

                      returnthis.findClass(name);

                  }

                  protected Class<?> findClass(String name) throws ClassNotFoundException {

                      //假設(shè)此處只是到工程以外的特定目錄D:/library下去加載類

                      具體實(shí)現(xiàn)代碼省略

                  }

          }

              通過前面的分析我們已經(jīng)知道,用戶自定義類加載器(WrongClassLoader)的默

                 認(rèn)的類加載器是系統(tǒng)類加載器,但是現(xiàn)在問題4種的結(jié)論就不成立了。大家可以簡(jiǎn)

                 單測(cè)試一下,現(xiàn)在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工

                 程類路徑上的類都加載不上了。

                 //問題5測(cè)試代碼一

          publicclass WrongClassLoaderTest {

                  publicstaticvoid main(String[] args) {

                     try {

                         WrongClassLoader loader = new WrongClassLoader();

                         Class classLoaded = loader.loadClass("beans.Account");

                         System.out.println(classLoaded.getName());

                         System.out.println(classLoaded.getClassLoader());

                     } catch (Exception e) {

                         e.printStackTrace();

                     }

                  }

          }

          (說明:D:"classes"beans"Account.class物理存在的)

          輸出結(jié)果:

          java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系統(tǒng)找不到指定的路徑。)

              at java.io.FileInputStream.open(Native Method)

              at java.io.FileInputStream.<init>(FileInputStream.java:106)

              at WrongClassLoader.findClass(WrongClassLoader.java:40)

              at WrongClassLoader.loadClass(WrongClassLoader.java:29)

              at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

              at java.lang.ClassLoader.defineClass1(Native Method)

              at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

              at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

              at WrongClassLoader.findClass(WrongClassLoader.java:43)

              at WrongClassLoader.loadClass(WrongClassLoader.java:29)

              at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

          Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object

              at java.lang.ClassLoader.defineClass1(Native Method)

              at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

              at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

              at WrongClassLoader.findClass(WrongClassLoader.java:43)

              at WrongClassLoader.loadClass(WrongClassLoader.java:29)

              at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

          這說明,連要加載的類型的超類型java.lang.Object都加載不到了。這里列舉的由于覆寫loadClass)引起的邏輯錯(cuò)誤明顯是比較簡(jiǎn)單的,實(shí)際引起的邏輯錯(cuò)誤可能復(fù)雜的多。

          //問題5測(cè)試二

          //用戶自定義類加載器WrongClassLoader.Java(不覆寫loadClass邏輯)

          publicclassWrongClassLoaderextends ClassLoader {

                  protected Class<?> findClass(String name) throws ClassNotFoundException {

                      //假設(shè)此處只是到工程以外的特定目錄D:/library下去加載類

                      具體實(shí)現(xiàn)代碼省略

                  }

          }

          將自定義類加載器代碼WrongClassLoader.Java做以上修改后,再運(yùn)行測(cè)試代碼,輸出結(jié)果如下:

          beans.Account

          WrongClassLoader@1c78e57

          這說明,beans.Account加載成功,且是由自定義類加載器WrongClassLoader加載。

          這其中的原因分析,我想這里就不必解釋了,大家應(yīng)該可以分析的出來了。

          2.      2、正確設(shè)置父類加載器

          通過上面問題4和問題5的分析我們應(yīng)該已經(jīng)理解,個(gè)人覺得這是自定義用戶類加載器時(shí)最重要的一點(diǎn),但常常被忽略或者輕易帶過。有了前面JDK代碼的分析作為基礎(chǔ),我想現(xiàn)在大家都可以隨便舉出例子了。

          3.      3、保證findClassString )方法的邏輯正確性

          事先盡量準(zhǔn)確理解待定義的類加載器要完成的加載任務(wù),確保最大程度上能夠獲取到對(duì)應(yīng)的字節(jié)碼內(nèi)容。

          4.6    如何在運(yùn)行時(shí)判斷系統(tǒng)類加載器能加載哪些路徑下的類?

          一是可以直接調(diào)用ClassLoader.getSystemClassLoader()或者其他方式獲取到系統(tǒng)類加載器(系統(tǒng)類加載器和擴(kuò)展類加載器本身都派生自URLClassLoader),調(diào)用URLClassLoader中的getURLs()方法可以獲取到;

          二是可以直接通過獲取系統(tǒng)屬性java.class.path 來查看當(dāng)前類路徑上的條目信息 , System.getProperty("java.class.path")

          4.7    如何在運(yùn)行時(shí)判斷標(biāo)準(zhǔn)擴(kuò)展類加載器能加載哪些路徑下的類?

          方法之一:

          try {
                         URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();

                        for (int i = 0; i < extURLs.length; i++) {

                               System.out.println(extURLs[i]);

                        }

                 } catch (Exception e) {//…}

                 本機(jī)對(duì)應(yīng)輸出如下:

          file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar

          file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar

          file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar

          file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar

          5       總結(jié):

          寫這篇文章的初衷是通過分析JDK相關(guān)代碼來驗(yàn)證一些加載規(guī)則,核心就是借助雙親委派機(jī)制來分析一個(gè)加載請(qǐng)求處理的主要過程,所列舉的幾個(gè)簡(jiǎn)單的例子實(shí)際意義不大,因?yàn)橛龅降那闆r往往比例子情況復(fù)雜的多。

          下一篇文章,我會(huì)重點(diǎn)分析一下Eclipse的插件類加載器,并分析一下插件環(huán)境下的類加載和普通java應(yīng)用場(chǎng)景中的類加載有什么不同,并會(huì)提供一個(gè)比較完整的類加載器。

          插件類加載器就是一個(gè)由eclipse開發(fā)的一個(gè)用戶自定義類加載器,所以分析時(shí)候用到的一些基本的東西都是在這篇文章中涉及到的。

              這篇文章寫的時(shí)候時(shí)間比較緊,亂糟糟的,大家見諒。中間參考了JVM規(guī)范、jdk文檔和代碼、《Inside Java Virtual Machine》一書等資料。

              文章中肯定包含了一些錯(cuò)誤,歡迎指出,謝謝!



          本博客中的所有文章、隨筆除了標(biāo)題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請(qǐng)注明出處,謝謝!

          posted on 2008-08-08 10:19 zhuxing 閱讀(39890) 評(píng)論(14)  編輯  收藏 所屬分類: Java

          評(píng)論

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          前兩個(gè)圖片看不見.
          2008-08-08 14:16 | xpf7622

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          不錯(cuò) 難得有寫這么深入的中文文章 頂!!
          2008-08-30 11:21 | advincenting

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          高手?。?
          真正的高手??!
          前輩搞了多少年JAVA了,現(xiàn)在在什么公司做架構(gòu)?
          2008-09-19 22:56 | 家佳

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          哥,前面幾張圖看不到
          2008-09-24 20:32 | BlackGabriel

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          很黃,很暴力,真的非常好!高手中的高手啊……微軟的架構(gòu)師吧!
          2010-12-17 15:35 | zw

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          膜拜中...
          2011-10-21 10:08 | ywm

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          真心膜拜啊。。。。
          2012-05-24 21:56 | ip

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          寫的很好,轉(zhuǎn)了,但是文中有一處錯(cuò)誤:"單參數(shù)版本的forName方法默認(rèn)是不完成初始化的",正好說反了,單參數(shù)版的默認(rèn)是進(jìn)行初始化的。
          2012-06-20 23:53 | 孫欽旺

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          高手,頂
          2013-03-27 17:30 | zqj

          # re: 【原創(chuàng)】Java類加載原理解析[未登錄]  回復(fù)  更多評(píng)論   

          高淫啊,我要什么時(shí)候才能到你這種水平呢
          2013-03-29 20:36 | kevin

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          @孫欽旺
          看的很仔細(xì),完全同意,估計(jì)樓主記反了,Class.forName單個(gè)參數(shù)是默認(rèn)需要初始化的,而loadClass當(dāng)個(gè)參數(shù)默認(rèn)是不會(huì)初始化的,第二個(gè)參數(shù)默認(rèn)是fasle。
          2013-09-20 15:28 | weisongbai

          # re: 【原創(chuàng)】Java類加載原理解析[未登錄]  回復(fù)  更多評(píng)論   

          恩,講解的很清晰,不錯(cuò)的文章!
          2013-12-09 14:34 | David

          # re: 【原創(chuàng)】Java類加載原理解析  回復(fù)  更多評(píng)論   

          寫的非常好,搞清了以前不明白的一些東西,轉(zhuǎn)載了
          2014-06-17 09:50 | 獅子愛睡覺

          # re: 【原創(chuàng)】Java類加載原理解析[未登錄]  回復(fù)  更多評(píng)論   

          或者其他方式獲取到系統(tǒng)類加載器(系統(tǒng)類加載器和擴(kuò)展類加載器本身都派生自URLClassLoader)

          怎么理解???

          2016-06-14 16:11 | 111
          主站蜘蛛池模板: 黄龙县| 临清市| 喀喇沁旗| 方山县| 湘潭县| 北安市| 通江县| 宁海县| 资中县| 五家渠市| 仪征市| 手游| 临汾市| 屯留县| 大兴区| 措美县| 高雄县| 通州市| 西华县| 偃师市| 安远县| 陆丰市| 黎川县| 濉溪县| 延庆县| 莱州市| 庄河市| 临沧市| 阜南县| 吉木乃县| 鄄城县| 宕昌县| 宜章县| 平利县| 玉山县| 平顶山市| 综艺| 临湘市| 广丰县| 镇平县| 惠东县|