1. 程式人生 > >談談 Java 類加載機制

談談 Java 類加載機制

ace 類加載器 需要 als register ble ras getclass pac

概述

類加載器主要分為兩類,一類是 JDK 默認提供的,一類是用戶自定義的。 JDK 默認提供三種類加載器:

Bootstrap ClassLoader 啟動類加載器:每次執行 java 命令時都會使用該加載器為虛擬機加載核心類。該加載器是由 native code 實現,而不是 Java 代碼,加載類的路徑為 <JAVA_HOME>/jre/lib。特別的 <JAVA_HOME>/jre/lib/rt.jar 中包含了 sun.misc.Launcher 類, 而 sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader 都是 sun.misc.Launcher 的內部類,所以拓展類加載器和系統類加載器都是由啟動類加載器加載的。

Extension ClassLoader, 拓展類加載器:用於加載拓展庫中的類。拓展庫路徑為 <JAVA_HOME>/jre/lib/ext/。實現類為 sun.misc.Launcher$ExtClassLoader
System ClassLoader 系統類加載器:用於加載 CLASSPATH 中的類。實現類為 sun.misc.Launcher$AppClassLoader
用戶自定義的類加載器
Custom ClassLoader, 一般都是 java.lang.ClassLoder 的子類
正統的類加載機制是基於雙親委派的,也就是當調用類加載器加載類時,首先將加載任務委派給雙親,若雙親無法加載成功時,自己才進行類加載。

在實例化一個新的類加載器時,我們可以為其指定一個 parent,即雙親,若未顯式指定,則 System ClassLoader 就作為默認雙親。

具體的說,類加載任務是由 ClassLoader 的 loadClass() 方法來執行的,他會按照以下順序加載類:

通過 findLoadedClass() 看該類是否已經被加載。該方法為 native code 實現,若已加載則返回。
若未加載則委派給雙親,parent.loadClass(),若成功則返回。
若未成功,則調用 findClass() 方法加載類。java.lang.ClassLoader 中該方法只是簡單的拋出一個 ClassNotFoundException 所以,自定義的 ClassLoader 都需要 Override findClass() 方法。

類加載API
java.lang.ClassLoader
ClassLoader 是一個抽象類。
待加載的類必須用 The Java? Language Specification 定義的全類名,全類名的定義請查閱 The Form of a Binary。
給定一個全類名,類加載器應該去定位該類所在的位置。通用的策略是將全類名轉換為類文件路徑,然後通過類文件路徑在文件系統中定位。
每一個加載到內存的類都由一個 Class 對象來表示,每一個 Class 對象都有一個指向加載該類的類加載器的引用。但是數組的 Class 對象是由 Java 運行時環境創建的,通過 Class.getClassLoader() 方法返回的是數組元素的類加載器,若數組元素是基本類型,則返回 null,若類是由 Bootstrap ClassLoader 加載的話也是返回 null。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
public static void main(String[] args) {
// Object 類在 <java_home>/jre/lib/rt.jar 中,
// 由 Bootstrap ClassLoader 加載,由於該類加載器是由 native code 編寫
// 所以輸出為 null
Object[] objects = new Object[5];
System.out.println();
System.out.println(objects.getClass().getClassLoader());

    // ZipFileAttributes 類在 <java_home>/jre/lib/ext/zipfs.jar 中,
    // 由 Extension ClassLoader 加載,
    // 輸出為  sun.misc.Launcher$ExtClassLoader@4b67cf4d
    ZipFileAttributes[] attributes = new ZipFileAttributes[5];
    System.out.println();
    System.out.println(attributes.getClass().getClassLoader());

    // Main 類是自定義的類,
    // 默認由 System ClassLoader 加載,
    // 輸出為 sun.misc.Launcher$AppClassLoader@18b4aac2
    Main[] array = new Main[5];
    array[0] = new Main();
    System.out.println();
    System.out.println(array.getClass().getClassLoader());
}

}
ClassLoader 默認支持並行加載,但是其子類必須調用 ClassLoader.registerAsParallelCapable() 來啟用並行加載
一般來說,JVM 從本地文件系統加載類的行為是與平臺有關的。
defineClass() 方法可以將字節流轉換成一個 Class 對象。然後調用 Class.newInstance() 來創建類的實例
java.security.SecureClassLoader
增加了一層權限驗證,因為關註點不在安全,所以暫不討論。

java.net.URLClassLoader
該類加載器用來加載 URL 指定的 JAR 文件或目錄中的類和資源,以 / 結尾的 URL 認為是目錄,否則認為是 JAR 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 嘗試通過 URLClassLoader 來加載桌面下的 Test 類。
public class Main {
public static void main(String[] args) {
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File("/home/chen/Desktop/");
String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator))
.toString();
urls[0] = new URL(null, repository, streamHandler);

        ClassLoader loader = new URLClassLoader(urls);

        Class testClass = loader.loadClass("Test");

        // output:  java.net.URLClassLoader@7f31245a
        System.out.println(testClass.getClassLoader());
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

}
Tomcat 8.5.15類加載機制

Tomcat 使用正統的類加載機制(雙親委派),但部分地方做了改動。

Bootstrap classLoader 和 Extension classLoader 的作用不變。
System classLoader 正常情況下加載的是 CLASSPATH 下的類,但是 Tomcat 的啟動腳本並未使用該變量,而是從以下倉庫下加載類:
$CATALINA_HOME/bin/bootstrap.jar 包含了 Tomcat 的啟動類。在該啟動類中創建了 Common classLoader、Catalina classLoader、shared classLoader。因為 $CATALINA_BASE/conf/catalina.properties 中只對 common.loader 屬性做了定義,server.loader 和 shared.loader 屬性為空,所以默認情況下,這三個 classLoader 都是 CommonLoader。具體的代碼邏輯可以查閱 org.apache.catalina.startup.Bootstrap 類的 initClassLoaders() 方法和 createClassLoader() 方法。
$CATALINA_BASE/bin/tomcat-juli.jar 包含了 Tomcat 日誌模塊所需要的實現類。
$CATALINA_HOME/bin/commons-daemon.jar。
Common classLoader 是位於 Tomcat 應用服務器頂層的公用類加載器。由其加載的類可以由 Tomcat 自身類和所有應用程序使用。掃描路徑由 $CATALINA_BASE/conf/catalina.properties 文件中的 common.loader 屬性定義。默認是 $CATALINA_HOME/lib。
catalina classLoader 用於加載服務器內部可見類,這些類應用程序不能訪問。
shared classLoader 用於加載應用程序共享類,這些類服務器不會依賴。
Webapp classLoader 。每個應用程序都會有一個獨一無二的 webapp classloader,他用來加載本應用程序 /WEB-INF/classes 和 /WEB-INF/lib 下的類。
特別的:

Webapp classLoader 的默認行為會與正常的雙親委派模式不同:

從 Bootstrap classloader 加載。
若沒有,從 /WEB-INF/classes 加載。
若沒有,從 /WEB-INF/lib/*.jar 加載。
若沒有,則依次從 System、Common、shared 加載(該步驟使用雙親委派)。
當然了,我們也可以通過配置來使 Webapp classLoader 嚴格按照雙親委派模式加載類:

通過在工程的 META-INF/context.xml(和 WEB-INF/classes 在同一目錄下) 配置文件中添加 <Loader delegate="true"/>
因為 Webapp classLoader 的實現類是 org.apache.catalina.loader.WebappLoader,他有一個屬性叫 delegate, 用來控制類加載器的加載行為,默認為 false,我們可以使用 set 方法,將其設為 true 來啟用嚴格雙親委派加載模式。
嚴格雙親委派模式加載步驟:

從 Bootstrap classloader 加載。
若沒有,則依次從 System、Common、shared 加載。
若沒有,從 /WEB-INF/classes 加載。
若沒有,從 /WEB-INF/lib/*.jar 加載。

談談 Java 類加載機制