1. 程式人生 > >Java虛擬機器-類載入器和類載入過程

Java虛擬機器-類載入器和類載入過程

類載入器

java.lang.ClassLoader類及其子類可以讓java程式碼動態地載入到JVM中。每一個類都有載入它的ClassLoader的引用。每一個類載入器類都有一個載入它的父類載入器,類載入器的頂端稱為啟動類載入器(Bootstrap ClassLoader),啟動類載入器由c++實現。

邏輯上結構是:

Bootstrap ClassLoader
        |
 ExtClassLoader extends URLClassLoader extends SecureClassLoader extends ClassLoader
        |
 AppClassLoader extends URLClassLoader extends SecureClassLoader extends ClassLoader
        |
 自定義類載入器

實際上它們是通過組合方式訪問父載入器。

  • Bootstrap ClassLoader : 啟動類載入器。負責載入$JAVA_HOME/lib 目錄下的類。
  • ExtClassLoader :擴充套件類載入器。負責載入$JAVA_HOME/lib/ext目錄下的類。 它的parent載入器為空,表示為Bootstrap ClassLoader。
  • AppClassLoader :應用類載入器。負責載入應用類,即classpath指定目錄下的類。它的parent載入器為ExtClassLoader。Launcher在啟動時會呼叫Thread.currentThread().setContextClassLoader()方法,將當前執行緒的類載入器設定為AppClassLoader。

  • URLClassLoader : 從指定jar檔案或目錄載入類

  • SecureClassLoader :確保安全並正確地將類裝入到 Java 執行時中。 主要作用:

    1. 確保以正確的順序完成對類的搜尋。當 JVM 需要一個類時,SecureClassLoader 首先檢視由 JVM 的類路徑所引用的檔案是否可用。類路徑中的檔案特指完全可信的類,是 Java 執行時的一部分。例如,隨 JVM 一起提供的所有程式碼包含在類路徑中,因此被認為是可信的程式碼。如果在類路徑中沒有,可以搜尋應用程式定義的位置(例如,URL 請求中的 Web 伺服器)。最後,程式碼可以是“Java 標準擴充套件”(Java Standard Extension)的一部分,是在主機檔案系統中可用的一組類,但不是 JVM 類路徑的一部分。“標準擴充套件”中的類通常位於主機系統(例如,工作站或個人計算機)的磁碟驅動器上,但這些類不是 JVM 的完全可信的執行時類的一部分。

    2. 給裝入到 JVM 中的類建立並設定 ProtectionDomain 資訊。當 SecureClassLoader 將一個類裝入到 JVM 中時,用來簽發類檔案的 codebase URL 和數字證書(如果存在)用來建立 CodeSource。CodeSource 用來定位(或例項化)類的 ProtectionDomain。ProtectionDomain 包含授予類的 Pemission。一旦類檔案被裝入到 JVM 中,SecureClassLoader 就將恰當的 ProtectionDomain 分配給類。這個 ProtectionDomain 資訊以及特別是 ProtectionDomain 中的 Pemission 用來確定執行時期間的訪問控制。

建立ExtClassLoader和AppClassLoader 原始碼:

sun.misc.Launcher#Launcher() 方法:


     // Create the extension class loader
    ClassLoader extcl;
    try {
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader");
    }

    // Now create the class loader to use to launch the application
    try {
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader");
    }

    // Also set the context class loader for the primordial thread.
    Thread.currentThread().setContextClassLoader(loader);

類載入過程

  1. 啟動虛擬機器,建立一個Bootsrap ClassLoader;
  2. Bootstrap ClassLoader 建立ExtClassLoader,父載入器為null(表示為Bootsrap ClassLoader);
  3. Bootstrap ClassLoader 建立 AppClassLoader,父載入器為ExtClassLoader;
  4. 當需要載入某個類時,通過呼叫AppClassLoader.loadClass()方法進行載入。這個方法的處理過程為:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 檢視類是否已載入過
            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); //如果沒有找到類,則丟擲ClassNotFoundException異常
    
                    // 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;
        }
    }

總的原則

類在載入時會逐級向上委託其父載入器進行載入,如果它們都載入不了,則由它自已載入。