深度分析Java的ClassLoader機制

原文:深度分析Java的ClassLoader機制(源碼級別)java

先來看Java程序是怎麼工做的
在這裏插入圖片描述程序員

爲了更好的理解類的加載機制,咱們來深刻研究一下ClassLoader和他的loadClass()方法。數組

源碼分析

public abstract class ClassLoader

ClassLoader類是一個抽象類,sun公司是這麼解釋這個類的:安全

/**
 * A class loader is an object that is responsible for loading classes. The
 * class <tt>ClassLoader</tt> is an abstract class.  Given the <a
 * href="#name">binary name</a> of a class, a class loader should attempt to
 * locate or generate data that constitutes a definition for the class.  A
 * typical strategy is to transform the name into a file name and then read a
 * "class file" of that name from a file system.
**/

大體意思以下:less

class loader是一個負責加載classes的對象,ClassLoader類是一個抽象類,須要給出類的二進制名稱,class loader嘗試定位或者產生一個class的數據,一個典型的策略是把二進制名字轉換成文件名而後到文件系統中找到該文件。dom

接下來咱們看loadClass方法的實現方式:jvm

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

仍是來看sun公司對該方法的解釋:ide

/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <p><ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     */

大體內容以下:函數

使用指定的二進制名稱來加載類,這個方法的默認實現按照如下順序查找類:
調用findLoadedClass(String)方法檢查這個類是否被加載過
使用父加載器調用loadClass(String)方法,若是父加載器爲Null,類加載器裝載虛擬機內置的加載器調用findClass(String)方法裝載類,
若是,按照以上的步驟成功的找到對應的類,而且該方法接收的resolve參數的值爲true,那麼就調用resolveClass(Class)方法來處理類。
ClassLoader的子類最好覆蓋findClass(String)而不是這個方法。
除非被重寫,這個方法默認在整個裝載過程當中都是同步的(線程安全的)。源碼分析

接下來,咱們開始分析該方法。

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

該方法的訪問控制符是protected,也就是說該方法同包內和派生類中可用,返回值類型Class

這裏用到泛型。這裏使用通配符?做爲泛型實參表示對象能夠 接受任何類型(類類型)。由於該方法不知道要加載的類究竟是什麼類,因此就用了通用的泛型。
String name要查找的類的名字;boolean resolve,一個標誌,true表示將調用resolveClass(c)處理該類。
throws ClassNotFoundException 該方法會拋出找不到該類的異常,這是一個非運行時異常。

synchronized (getClassLoadingLock(name)) 看到這行代碼,咱們能知道的是,這是一個同步代碼塊,那麼synchronized的括號中放的應該是一個對象。咱們來看getClassLoadingLock(name)方法的做用是什麼:

protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

以上是getClassLoadingLock(name)方法的實現細節,咱們看到這裏用到變量parallelLockMap ,根據這個變量的值進行不一樣的操做,若是這個變量是Null,那麼直接返回this,若是這個屬性不爲Null,那麼就新建一個對象,而後在調用一個putIfAbsent(className, newLock);方法來給剛剛建立好的對象賦值,這個方法的做用咱們一會講。那麼這個parallelLockMap變量又是哪來的那,咱們發現這個變量是ClassLoader類的成員變量:

private final ConcurrentHashMap<String, Object> parallelLockMap;

這個變量的初始化工做在ClassLoader的構造函數中:

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

這裏咱們能夠看到構造函數根據一個屬性ParallelLoadersRegistered狀態的不一樣來給parallelLockMap 賦值。 我去,隱藏的好深,好,咱們繼續挖,看看這個ParallelLoaders又是在哪賦值的呢?咱們發現,在ClassLoader類中包含一個靜態內部類private static class ParallelLoaders,在ClassLoader被加載的時候這個靜態內部類就被初始化。這個靜態內部類的代碼我就不貼了,直接告訴你們什麼意思,sun公司是這麼說的:Encapsulates the set of parallel capable loader types,意識就是說:封裝了並行的可裝載的類型的集合。

上面這個說的是否是有點亂,那讓咱們來整理一下:
首先,在ClassLoader類中有一個靜態內部類ParallelLoaders,他會指定的類的並行能力,若是當前的加載器被定位爲具備並行能力,那麼他就給parallelLockMap定義,就是new一個
ConcurrentHashMap<>(),那麼這個時候,咱們知道若是當前的加載器是具備並行能力的,那麼parallelLockMap就不是Null,這個時候,咱們判斷parallelLockMap是否是Null,若是他是null,說明該加載器沒有註冊並行能力,那麼咱們沒有必要給他一個加鎖的對象,getClassLoadingLock方法直接返回this,就是當前的加載器的一個實例。若是這個parallelLockMap不是null,那就說明該加載器是有並行能力的,那麼就可能有並行狀況,那就須要返回一個鎖對象。而後就是建立一個新的Object對象,調用parallelLockMap的putIfAbsent(className,
newLock)方法,這個方法的做用是:首先根據傳進來的className,檢查該名字是否已經關聯了一個value值,若是已經關聯過value值,那麼直接把他關聯的值返回,若是沒有關聯過值的話,那就把咱們傳進來的Object對象做爲value值,className做爲Key值組成一個map返回。而後不管putIfAbsent方法的返回值是什麼,都把它賦值給咱們剛剛生成的那個Object對象。
這個時候,咱們來簡單說明一下getClassLoadingLock(String className)的做用,就是:
爲類的加載操做返回一個鎖對象。爲了向後兼容,這個方法這樣實現:若是當前的classloader對象註冊了並行能力,方法返回一個與指定的名字className相關聯的特定對象,不然,直接返回當前的ClassLoader對象。

Class c = findLoadedClass(name);

在這裏,在加載類以前先調用findLoadedClass方法檢查該類是否已經被加載過,findLoadedClass會返回一個Class類型的對象,若是該類已經被加載過,那麼就能夠直接返回該對象(在返回以前會根據resolve的值來決定是否處理該對象,具體的怎麼處理後面會講)。 若是,該類沒有被加載過,那麼執行如下的加載過程,

try {
    if (parent != null) {
           c = parent.loadClass(name, false);
    } else {
            c = findBootstrapClassOrNull(name);
     }
} catch (ClassNotFoundException e) {
         // ClassNotFoundException thrown if class not found
          // from the non-null parent class loader
}

若是父加載器不爲空,那麼調用父加載器的loadClass方法加載類,若是父加載器爲空,那麼調用虛擬機的加載器來加載類。

若是以上兩個步驟都沒有成功的加載到類,那麼

c = findClass(name);

調用本身的findClass(name)方法來加載類。

這個時候,咱們已經獲得了加載以後的類,那麼就根據resolve的值決定是否調用resolveClass方法。resolveClass方法的做用是:

連接指定的類。這個方法給Classloader用來連接一個類,若是這個類已經被連接過了,那麼這個方法只作一個簡單的返回。不然,這個類將被按照 Java™規範中的Execution描述進行連接。

至此,ClassLoader類以及loadClass方法的源碼咱們已經分析完了。那麼,結合源碼的分析,咱們來總結一下:

總結

java中的類大體分爲三種:

一、系統類
二、擴展類
三、由程序員自定義的類

BootstrapClassLoader 負責加載 JVM 運行時核心類,這些類位於 JAVA_HOME/lib/rt.jar 文件中,咱們經常使用內置庫 java.xxx.* 都在裏面,好比 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。這個 ClassLoader 比較特殊,它是由 C 代碼實現的,咱們將它稱之爲「根加載器」。

ExtensionClassLoader 負責加載 JVM 擴展類,好比 swing 系列、內置的 js 引擎、xml 解析器 等等,這些庫名一般以 javax 開頭,它們的 jar 包位於 JAVA_HOME/lib/ext/*.jar 中,有不少 jar 包。

AppClassLoader 纔是直接面向咱們用戶的加載器,它會加載 Classpath 環境變量裏定義的路徑中的 jar 包和目錄。咱們本身編寫的代碼以及使用的第三方 jar 包一般都是由它來加載的。

類裝載方式,有兩種:

一、隱式裝載, 程序在運行過程當中當碰到經過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。
二、顯式裝載, 經過class.forName()等方法,顯式加載須要的類

類加載的動態性體現:

一個應用程序老是由n多個類組成,Java程序啓動時,並非一次把全部的類所有加載後再運行,它老是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節省了內存的開銷,由於java最先就是爲嵌入式系統而設計的,內存寶貴,這是一種能夠理解的機制,而用到時再加載這也是java動態性的一種體現

java類裝載器

Java中的類裝載器實質上也是類,功能是把類載入jvm中,值得注意的是jvm的類裝載器並非一個,而是三個,層次結構以下:

爲何要有三個類加載器,一方面是分工,各自負責各自的區塊,另外一方面爲了實現委託模型,下面會談到該模型

類加載器之間是如何協調工做的

前面說了,java中有三個類加載器,問題就來了,碰到一個類須要加載時,它們之間是如何協調工做的,即java是如何區分一個類該由哪一個類加載器來完成呢。 在這裏java採用了委託模型機制,這個機制簡單來說,就是「類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,若是Parent 找不到,那麼才由本身依照本身的搜索路徑搜索類」

下面舉一個例子來講明,爲了更好的理解,先弄清楚幾行代碼:

Public class Test{
    Public static void main(String[] arg){
        ClassLoader c  = Test.class.getClassLoader();  //獲取Test類的類加載器
        System.out.println(c); 
        ClassLoader c1 = c.getParent();  //獲取c這個類加載器的父類加載器
        System.out.println(c1);
        ClassLoader c2 = c1.getParent();//獲取c1這個類加載器的父類加載器
        System.out.println(c2);
  }
}

運行結果:

。。。AppClassLoader。。。

。。。ExtClassLoader。。。

Null

能夠看出Test是由AppClassLoader加載器加載的,AppClassLoaderParent 加載器是 ExtClassLoader,可是ExtClassLoaderParentnull 是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上並不存在Bootstrap Loader的類實體,因此在java程序代碼裏試圖打印出其內容時,咱們就會看到輸出爲null

類裝載器ClassLoader(一個抽象類)描述一下JVM加載class文件的原理機制

類裝載器就是尋找類或接口字節碼文件進行解析並構造JVM內部對象表示的組件,在java中類裝載器把一個類裝入JVM,通過如下步驟:

一、裝載:查找和導入Class文件
二、連接:其中解析步驟是能夠選擇的
(a)檢查:檢查載入的class文件數據的正確性
(b)準備:給類的靜態變量分配存儲空間
(c)解析:將符號引用轉成直接引用(在Java中, 一個Java類將會編譯成一個class文件. 在編譯時, Java類並不知道所引用的類的實際地址, 所以只能使用符號引用來代替. 好比org.simple.People類引用了org.simple.Language類, 在編譯時People類並不知道Language類的實際內存地址, 所以只能使用符號org.simple.Language來表示Language類的地址.)
三、初始化:對靜態變量,靜態代碼塊執行初始化工做

類裝載工做由ClassLoder和其子類負責。JVM在運行時會產生三個ClassLoader根裝載器ExtClassLoader(擴展類裝載器)和AppClassLoader(應用類加載器)。

其中根裝載器不是ClassLoader的子類,由C++編寫,所以在java中看不到他,負責裝載JRE的核心類庫,如JRE目錄下的rt.jar,charsets.jar等。

ExtClassLoaderClassLoder的子類,負責裝載JRE擴展目錄ext下的jar類包。

AppClassLoader負責裝載classpath路徑下的類包。

這三個類裝載器存在父子層級關係,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoaderAppClassLoader的父裝載器。默認狀況下使用AppClassLoader裝載應用程序的類。

Java裝載類使用「全盤負責委託機制」。「全盤負責」是指當一個ClassLoder裝載一個類時,除非顯示的使用另一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入;「委託機制」是指先委託父類裝載器尋找目標類,只有在找不到的狀況下才從本身的類路徑中查找並裝載目標類。這一點是從安全方面考慮的,試想若是一我的寫了一個惡意的基礎類(如java.lang.String)並加載到JVM將會引發嚴重的後果,但有了全盤負責制,java.lang.String永遠是由根裝載器來裝載,避免以上狀況發生 除了JVM默認的三個ClassLoder之外,第三方能夠編寫本身的類裝載器,以實現一些特殊的需求。類文件被裝載解析後,在JVM中都有一個對應的java.lang.Class對象,提供了類結構信息的描述。數組,枚舉及基本數據類型,甚至void都擁有對應的Class對象。Class類沒有public的構造方法,Class對象是在裝載類時由JVM經過調用類裝載器中的defineClass()方法自動構造的。

相關文章
相關標籤/搜索