程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java類加載器(一)——類加載器層次與模型

Java類加載器(一)——類加載器層次與模型

編輯:JAVA綜合教程

Java類加載器(一)——類加載器層次與模型


類加載器

??虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作放到Java虛擬機外部去實現,以便讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類加載器”。


類加載器層次(等級)

??從JVM的角度來講,只存在兩種不同的類加載器。
??第一類是啟動類加載器(Bootstrap ClassLoader):這個類加載器主要加載JVM自身工作需要的類。這個類加載器由C++語言實現(特指HotSpot),是虛擬機自身的一部分。負責將存放在%JAVA_HOME%\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中,並且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類加載到虛擬機內存中。
??另一類就是所有其他的類加載器,這些加載器都是由java實現,獨立於虛擬機外部。
??Extension ClassLoader:這個類即在其有sun.misc.Launcher$ExtClassLoader實現,它負責加載%JAVA_HOME%\lib\ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。
??Application ClassLoader:這個類加載器由sun.misc.Launcher$AppClassLoader實現。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也成它為系統類加載器。它負責加載用戶類路徑上指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
??AppClassLoader的parent是ExtClassLoader。很多文章在介紹ClassLoader層次的結構時把Bootstrap ClassLoader也列在ExtClassLoader的上一級中,其實Bootstrap ClassLoader並不屬於JVM的類等級層次,因為Bootstrap ClassLoader沒有遵守ClassLoader的加載股則。另外Bootstrap ClassLoader沒有子類,ExtClassLoader的父類也不是Bootstrap ClassLoader,ExtClassLoader並沒有父類,我們在應用中能提取到的頂層父類是ExtClassLoader.
代碼舉例:

ClassLoader cl = Thread.currentThread().getContextClassLoader();
        System.out.println(cl.toString());
        System.out.println(cl.getParent());
        System.out.println(cl.getParent().getParent());

??輸出結果:

[email protected]
[email protected]
null

如何獲得ClassLoader

this.getClass().getClassLoader();//使用當前類的ClassLoader Thread.currentThread().getContextClassLoader();//使用當前線程的ClassLoader ClassLoader.getSystemClassLoader();//使用系統ClassLoader

??代碼舉例:

    public void test()
    {
        System.out.println(this.getClass().getClassLoader().toString());
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader());
    }

??輸出:

[email protected]
[email protected]
[email protected]

[Tips]如何獲得類的class屬性:
1. Class.forName(類路徑全名);
2. this.getClass();
3. 使用.class,比如String.class
4. 對於基本數據類型有:Class c1 = int.class(class只是約定標記,不是成員屬性) 或者Class c2 = Integer.TYPE

??JVM加載class文件到內存有兩種方式:
??1. 隱式加載:所謂的隱式加載就是不通過在代碼裡調用ClassLoader來記載需要的類,而是通過JVM來自動加載需要的類到內存的方式。例如,當我們在類中集成或者引用某個類是,JVM在解析當前這個類時發現引用的類不在內存中,那麼就會自動將這些類加載到內存中。
??2. 顯示加載:相反的顯示加載就是我們在代碼中通過調用ClassLoader類來加載一個類的方式,例如,調用this.getClass.getClassLoader().loadClass()或者Class.forName(),或者我們自己實現的ClassLoader的findClass()方法等。


雙親委派模型

??雙親委派模型要求畜類鼎城的啟動類加載器(Bootstrap ClassLoader)之外,其余的類加載器都應當有自己的父類加載器。這裡類加載器之間的父子關系一般不會以inheritance的關系實現而是都是使用組合Composition關系來復用父類加載器的代碼
??雙親委派模型不是一個強制性的約束模型,而是java設計者推薦給開發者的一種類加載器實現方式。在java的世界中大部分的類加載器都遵循這個模型,但也有例外,到目前為止,雙親委派模型主要出現過3此較大的“被破壞”情況,這個稍後再闡述。
??雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。
??雙親委托機制的作用是防止系統jar包被本地替換,因為查找方法過程都是從最底層開始查找。 因此,一般我們自定義的classloader都需要采用這種機制,我們只需要繼承java.lang.ClassLoader實現findclass即可,如果需要更多控制,自定義的classloader就需要重寫loadClass方法了,比如tomcat的加載過程,這個比較復雜,可以通過其他文檔資料查看相關介紹。
??雙親委派模型對於保證java程序的穩定運作很重要,實現體現在java.lang.ClassLoader的loadClass()方法中,如下:

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;
        }
    }

??loadClass()方法的加載步驟為:
??1. 調用 Class c = findLoadedClass(name);來檢查是否已經加載類;
??2. 在父類加載器上調用loadClass方法:
???c = parent.loadClass(name, false);
??如果父類加載器為null,則使用虛擬機的內置類加載器:
???c = findBootstrapClassOrNull(name);
??3. 在父類加載器無法加載的時候再調用本身的findClass方法來進行類加載:
???c = findClass(name);

每個ClassLoader加載Class的過程是:
1.檢測此Class是否載入過(即在cache中是否有此Class),如果有到8,如果沒有到2;
2.如果parent classloader不存在(沒有parent,那parent一定是bootstrap classloader了),到4;
3.請求parent classloader載入,如果成功到8,不成功到5;
4.請求jvm從bootstrap classloader中載入,如果成功到8;
5.尋找Class文件(從與此classloader相關的類路徑中尋找)。如果找不到則到7;
6.從文件中載入Class,到8;
7.拋出ClassNotFoundException;
8.返回Class.


破壞雙親委派模型

??上文提到了到目前為止,雙親委派模型出現過3次較大規模的“被破壞”的情況,這裡詳細闡述一下。
??第一次。發生在雙親委派模型出現之前(jdk1.2發布之前)。由於雙親委派模型在jdk1.2之後才被引入,而類加載器和抽象類java.lang.ClassLoader則在jdk1.0時代就已經存在,面對已經存在的用戶自定義類加載器的實現代碼,java設計者引入雙親委派模型時不得不做出了一些妥協。歷史已經成為過去,具體的在此不贅述,需要注意的是jdk1.2之後不提倡用戶再去覆蓋loadClass()方法,而應當把自己的類加載邏輯寫到findClass()中,這樣保證符合雙親委派模型的規則。
??第二次。由模型本身的缺陷所導致的,雙親委派模型很好地解決了各個類加載器的基礎類的統一問題。當父類加載器需要請求子類加載器去完成類加載動作,比如JNDI服務:它的代碼由啟動類加載器去加載,但JNDI的目的是對資源進行集中管理和查找,它需要調用由獨立廠商實現並部署在應用程序的Classpath下的JNDI提供者(SPI)的代碼,但是啟動類加載器不“認識”這些代碼。這是就要用到了線程上下文加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設置。
??這裡怎麼又出來一個context classloader呢?它有什麼用呢?我們在建立一個線程Thread的時候,可以為這個線程通過setContextClassLoader方法來指定一個合適的classloader作為這個線程的context classloader,當此線程運行的時候,我們可以通過getContextClassLoader方法來獲得此context classloader,就可以用它來載入我們所需要的Class。默認的是system classloader。利用這個特性,我們可以“打破”classloader委托機制了,父classloader可以獲得當前線程的context classloader,而這個context classloader可以是它的子classloader或者其他的classloader,那麼父classloader就可以從其獲得所需的 Class,這就打破了只能向父classloader請求的限制了。這個機制可以滿足當我們的classpath是在運行時才確定,並由定制的 classloader加載的時候,由system classloader(即在jvm classpath中)加載的class可以通過context classloader獲得定制的classloader並加載入特定的class(通常是抽象類和接口,定制的classloader中是其實現),例如web應用中的servlet就是用這種機制加載的.
??第三次。 由於用戶對程序動態性的追求而導致的,這裡所說的“動態性”指的是當前一些非常“熱門”的名稱:代碼熱替換、模塊熱部署等,類似於鼠標鍵盤熱拔插。具體的可以看一下OSGi,在OSGi環境下,類加載器不再是雙親委派模型中的樹型結構,而是一種網狀結構。具體的可以翻閱一些相關資料。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved