ClassLoader 機制

 

JAVA啓動後,是通過JVM各級ClassLoader來加載各個類到內存。爲了更加了解加載過程,我經過分析和寫了一個簡單的ClassLoader來粗淺的分析它的原理。java

JVM的ClassLoader分三層,分別爲Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他們不是類繼承的父子關係,是邏輯上的上下級關係。mysql

Bootstrap ClassLoader是啓動類加載器,它是用C++編寫的,從%jre%/lib目錄中加載類,或者運行時用-Xbootclasspath指定目錄來加載。sql

Extension ClassLoader是擴展類加載器,從%jre%/lib/ext目錄加載類,或者運行時用-Djava.ext.dirs制定目錄來加載。編程

System ClassLoader,系統類加載器,它會從系統環境變量配置的classpath來查找路徑,環境變量裏的.表示當前目錄,是經過運行時-classpath或-Djava.class.path指定的目錄來加載類。數組

 

通常自定義的Class Loader能夠從java.lang.ClassLoader繼承,不一樣classloader加載相同的類,他們在內存也不是相等的,即它們不能互相轉換,會直接拋異常。java.lang.ClassLoader的核心加載方法是loadClass方法,如:緩存

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

         throws ClassNotFoundException

    {

         // First, check if the class has already been loaded

         Class c = findLoadedClass(name);

         if (c == null) {

             try {

                   if (parent != null) {

                       c = parent.loadClass(name, false);

                   } else {

                       c = findBootstrapClass0(name);

                   }

             } catch (ClassNotFoundException e) {

                 // If still not found, then invoke findClass in order

                 // to find the class.

                 c = findClass(name);

             }

         }

         if (resolve) {

             resolveClass(c);

         }

         return c;

    }

 

  經過上面加載過程,咱們能知道JVM默認是雙親委託加載機制,即首先判斷緩存是否有已加載的類,若是緩存沒有,但存在父加載器,則讓父加載器加載,若是不存在父加載器,則讓Bootstrap ClassLoader去加載,若是父類加載失敗,則調用本地的findClass方法去加載。tomcat

      雙親委託機制的做用是防止系統jar包被本地替換,由於查找方法過程都是從最底層開始查找。 所以,通常咱們自定義的classloader都須要採用這種機制,咱們只須要繼承java.lang.ClassLoader實現findclass便可,若是須要更多控制,自定義的classloader就須要重寫loadClass方法了,好比tomcat的加載過程,這個比較複雜,能夠經過其餘文檔資料查看相關介紹。app

      各個ClassLoader加載相同的類後,他們是不互等的,這個當涉及多個ClassLoader,而且有經過當前線程上線文獲取ClassLoader後轉換特別須要注意,能夠經過線程的setContextClassLoader設置一個ClassLoader線程上下文,而後再經過Thread.currentThread().getContextClassLoader()獲取當前線程保存的Classloader。可是自定義的類文件,放到Bootstrap ClassLoader加載目錄,是不會被Bootstrap ClassLoader加載的,由於做爲啓動類加載器,它不會加載本身不熟悉的jar包的,而且類文件必須打包成jar包放到加載器加載的根目錄,纔可能被擴展類加載器所加載。jvm

 

Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的關係以下:this

Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。

可是這並非繼承關係,只是語義上的定義,基本上,每個ClassLoader實現,都有一個Parent ClassLoader。

 

能夠經過ClassLoader的getParent方法獲得當前ClassLoader的parent。Bootstrap ClassLoader比較特殊,由於它不是java class因此Extension ClassLoader的getParent方法返回的是NULL。

 

因爲一些特殊的需求,咱們可能須要定製ClassLoader的加載行爲,這時候就須要自定義ClassLoader了.

自定義ClassLoader須要繼承ClassLoader抽象類,重寫findClass方法,這個方法定義了ClassLoader查找class的方式。

主要能夠擴展的方法有:

findClass          定義查找Class的方式

defineClass       將類文件字節碼加載爲jvm中的class

findResource    定義查找資源的方式

 

現已有的ClassLoader實現有以下幾種:

  • java.net.URLClassLoader
  • java.security.SecureClassLoader
  • java.rmi.server.RMIClassLoader
  • sun.applet.AppletClassLoader

Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子類。

這個是URLClassLoader的構造方法:

public URLClassLoader(URL[] urls, ClassLoader parent)

public URLClassLoader(URL[] urls)

 

urls參數是須要加載的ClassPath url數組,能夠指定parent ClassLoader,不指定的話默認以當前調用類的ClassLoader爲parent。

因爲classloader 加載類用的是全盤負責委託機制。所謂全盤負責,便是當一個classloader加載一個Class的時候,這個Class所依賴的和引用的全部 Class也由這個classloader負責載入,除非是顯式的使用另一個classloader載入。

因此,當咱們自定義的classloader加載成功了com.company.MyClass之後,MyClass裏全部依賴的class都由這個classLoader來加載完成。

 

 


 

Class.forName() 與 load.loadClass()的區別:

 

Class.forName("xx.xx")等同於Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第二個參數(bool)表示裝載類的時候是否初始化該類,即調用類的靜態塊的語句及初始化靜態成員變量。

ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也能夠用(ClassLoader.getSystemClassLoader())

Class cls = loader.loadClass("xx.xx"); //這句話沒有執行初始化,其實與Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")執行的是更底層的操做。

只有執行cls.NewInstance()纔可以初始化類,獲得該類的一個實例

 

  Class的裝載分了三個階段,loading,linking和initializing,分別定義在The Java Language Specification的12.2,12.3和12.4。
Class.forName(className) 其實是調用Class.forName(className, true, this.getClass().getClassLoader())。注意第二個參數,是指Class被loading後是否是必須被初始化。
ClassLoader.loadClass(className)實際上調用的是ClassLoader.loadClass(name, false),第二個參數指出Class是否被link。
區別就出來了。Class.forName(className)裝載的class已經被初始化,而ClassLoader.loadClass(className)裝載的class尚未被link。
forName支持數組類型,loadClass不支持數組

  通常狀況下,這兩個方法效果同樣,都能裝載Class。但若是程序依賴於Class是否被初始化,就必須用Class.forName(name)了。
例如,在JDBC編程中,常看到這樣的用法,Class.forName("com.mysql.jdbc.Driver"),若是換成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
爲何呢?打開com.mysql.jdbc.Driver的源代碼看看,

//
// Register ourselves with the DriverManager
//
static {
  try {
    java.sql.DriverManager.registerDriver(new Driver());
  } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
  }
}

 

  原來,Driver在static塊中會註冊本身到java.sql.DriverManager。而static塊就是在Class的初始化中被執行。因此這個地方就只能用Class.forName(className)。

相關文章
相關標籤/搜索