理解Java類加載機制(譯文)

理解java類加載機制

你想寫類加載器?或者你遇到了ClassCastException異常,或者你遇到了奇怪的LinkageError狀態約束異常。應該仔細看看java類的加載處理了。java

什麼是類加載器以及它是如何對類進行加載的?

一個Java類是由java.lang.ClassLoader類的一個實例加載的。因爲java.lang.ClassLoader本身自己是一個抽象類因此一個類加載器只可以是java.lang.ClassLoader類的具體子類的實例。若是是這種狀況,那麼哪個類加載器來加載java.lang.ClassLoader這個類?(經典的"誰將會加載加載者"引導的問題)。事實證實JVM有一個內置的引導類加載器。引導加載器加載java.lang.ClassLoader和許多其餘java平臺類。sql

要加載一個具體的java類,例如com.acme.Foo,JVM調用java.lang.ClassLoader類的loadClass方法(事實上,JVM查找loadClassInternal方法-若是發現loadClassInternal方法則用loadClassInternal方法,不然JVM使用loadClass方法,而loadClassInternal方法會調用loadClass方法)。loadClass方法接收類名來加載類返回表示加載的類的java.lang.Class實例。事實上loadClass方法找到.class文件(或者URL)的實際字節,並調用defineClass方法來構造出java.lang.Class類的字節數組。加載器上調用loadClass方法的加載器稱之爲初始化加載器(即,JVM啓動加載使用這個加載器).可是,啓動加載器不是直接加載類的-而是可能委託給另一個類加載器(例如,它的父加載器)-它本身也可能委派給另一個加載器去加載等等。最終在委託鏈中的某些類加載器對象調用defineClass方法加載有關的類(com.acme.Foo)。
這個特殊的類加載器叫作com.acme.Foo的確切加載器。在運行時,一個java類是由類的徹底限定類名和和類加載器肯定其惟一性的。若是指定相同的類名(即,相同的徹底限定類名)的類是由兩個不一樣的類加載器加載的,那麼這些類是不一樣的-即便這些.class的字節碼是相同的而且都是從相同的位置進行加載的(相同的URL)。數組

有多少種類加載器而且它們是從哪裏加載類的?

即使是一個簡單的"hell world"java程序,也有至少3種類加載器。緩存

  1. 引導類加載器oracle

    • 加載java平臺基礎類(例如java.lang.Object,java.lang.Thread等)。
    • 加載rt.jar包種的類($JRE_HOME/lib/rt.jar)。
    • -Xbootclasspath參數能夠更改啓動類路徑-Xbootclasspath/p: 和 -Xbootclasspath/a:參數能夠前置/追加額外的引導目錄-必定要格外當心這樣作。在大多數狀況下,要避免隨意變動啓動類路徑。
    • 在Sun的實現中,只讀系統屬性sun.boot.class.path設置爲指向啓動類路徑。注意你不能在運行時修改這個屬性-若是你修改了修改也不會生效。
    • 這個加載器由java的null表示。例如,java.lang.Object.class.getClassLoader()方法將會返回null(還有其餘的類例如java.lang.Integer,java.awt.Frame,java.sql.DriverManager等)
  2. 擴展類加載器
    • 從已安裝的可選包中加載類。
    • 從$JRE_HOME/lib/ext目錄下加載jar文件。
    • 可使用-Djava.ext.dirs命令修改系統屬性java.ext.dirs來修改擴展目錄。
    • 在Sun的實現中,它是sun.misc.Launcher$ExtClassLoader的實例(事實上它是sun.misc.Launcher類的一個內部類)。
    • 在編寫代碼中。你可能讀取(只可以讀取!)系統通屬性java.ext.dirs來尋找哪一個目錄是擴展目錄。你不能在在運行期間修改這個屬性-即便你修改了修改也不會生效。
  3. 應用類加載器
    • 從應用的classpath中加載類。
    • 使用環境變量CLASSPATH(者-cp或者-classpath選項設置應用的classpath,若是CLASSPATH和-cp都找不到,則使用"."(當前目錄)。
    • 只讀系統屬性java.class.path是應用類路徑。你不能在運行期間修改這個屬性-即便你修改了修改也不會生效。
    • java.lang.ClassLoader.getSystemClassLoader()方法的返回值是這個加載器
    • 這個加載器也叫作"系統加載器"-不過不要將加載java"系統"類的啓動加載器混淆。
    • 這個加載器加載你應用的"main"(由main方法的類)。在Sun的實現中,它是sun.misc.Launcher$AppClassLoader的一個實例(事實上它是sun.misc.Launcher類的一個內部類)。
    • 默認的應用加載器用擴展加載器作爲它的父加載器。
    • 你可使用-Djava.system.class.loader命令更改應用的類加載器。這個值指定java.lang.ClassLoader的子類的名字.首先默認應用加載器加載已命名的類(這個類在CLASSPATH或者-cp)和建立一個它的實例.新建立的這個類的實例用於加載應用的main類。

一個類典型的類記載流程

讓咱們假設你正在運行一個"hello world" java程序。咱們來看一下類的加載流程。JVM用應用類加載器加載主方法(main)所在的類。若是你運行下面的程序spa

class Main {
    public static void main(String[] args) {
        System.out.println(Main.class.getClassLoader());
        javax.swing.JFrame f = new javax.swing.JFrame();
        f.setVisible(true);
        SomeAppClass s = new SomeAppClass();
}

它會打印以下內容
sun.misc.Launcher$AppClassLoader@17943a4code

每當一些其它的類引用在Main類中被解析時,JVM用Main所在類的明確的加載器-應用類加載器-作爲初始化加載器。在上面的列子中,爲了加載javax.swing.JFrame類JVM將使用應用類加載器作爲一個初始化加載器。即,JVM將用應用類應用作爲初始化加載器。即。JVM將調用loadClass()方法(loadClassInternal方法)在應用類加載器中。應用類加載器委託給擴展類加載器。
擴展加載器檢查這是不是一個啓動類(用私有方法 - ClassLoader.findBootstrapClass),啓動類加載器是否從rt.jar加載過它。
當SomeAppClass的引用類被解析時,JVM有着相同的過程-用應用類加載器作爲初始化加載器。
應用加載器委託給擴展加載器,擴展加載器檢查啓動加載器,啓動加載器找不到"SomeAppClass"類,
因而擴展加載器檢查"SomeAppClass"類是否在擴展jars裏,結果發現不在。
因而應用類加載器檢查在應用的CLASSPATH下的.class字節,若是找到了則進行加載,若是沒有找到,將會拋出NoClassDefFoundError異常。對象

總結:

  • Class是由具體的類加載器與類的徹底限定類名惟必定義的。blog

  • 若是具體的類加載器不一樣,即便.class字符是從文件系統中的相同位置進行加載的Classes也是不一樣的。get

  • 類加載器委託給父加載器進行加載。

  • 加載Bar類中引用的Foo類,JVM使用Bar類的確切的類加載器作爲初始化加載器。JVM會在Bar類的確切加載器上會調用loadClass()方法加載Foo類。

  • JVM緩存->運行時的類每次初始化加載都將被記錄。JVM將會緩存用於之後的解析。即,loadClass()方法不會對於每一次引用都調用。這能確保時間的不變性-即,一個類加載器不容許加載相同類名但字節碼不一樣的類。
    他是由緩存來實現的。好的類加載器應該經過調用ClassLoader得call()方法來檢查緩存。

原文連接

Understanding Java class loading https://blogs.oracle.com/sundararajan/entry/understanding_java_class_loading

相關文章
相關標籤/搜索