談談 Java 類加載機制

談談 Java 類加載機制

java程序媛之家

github.com/c-rainstorm/blog/blob/master/java/談談Java類加載機制.md

最近在學習 Tomcat 架構,其中很重要的一個模塊是類加載器,因為以前學習的不夠深入,所以趁這個機會好好把類加載機制搞明白。

概 述

談談 Java 類加載機制

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

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

用戶自定義的類加載器

1. Custom ClassLoader, 一般都是 java.lang.ClassLoder 的子類

正統的類加載機制是基於雙親委派的,也就是當調用類加載器加載類時,首先將加載任務委派給雙親,若雙親無法加載成功時,自己才進行類加載。

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

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

  1. 通過 findLoadedClass() 看該類是否已經被加載。該方法為 native code 實現,若已加載則返回。
  2. 若未加載則委派給雙親,parent.loadClass(),若成功則返回。
  3. 若未成功,則調用 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。

public class Main {

public static void main(String[] args) {

// Object 類在 <java>/jre/lib/rt.jar 中,/<java>

// 由 Bootstrap ClassLoader 加載,由於該類加載器是由 native code 編寫

// 所以輸出為 null

Object[] objects = new Object[5];

System.out.println();

System.out.println(objects.getClass().getClassLoader());

// ZipFileAttributes 類在 <java>/jre/lib/ext/zipfs.jar 中,/<java>

// 由 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 文件。

// 嘗試通過 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類加載機制

談談 Java 類加載機制

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

  • Bootstrap classLoader 和 Extension classLoader 的作用不變。
  • System classLoader 正常情況下加載的是 CLASSPATH 下的類,但是 Tomcat 的啟動腳本並未使用該變量,而是從以下倉庫下加載類:
  1. $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() 方法。
  2. $CATALINA_BASE/bin/tomcat-juli.jar 包含了 Tomcat 日誌模塊所需要的實現類。
  3. $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 的默認行為會與正常的雙親委派模式不同:

  1. 從 Bootstrap classloader 加載。
  2. 若沒有,從 /WEB-INF/classes 加載。
  3. 若沒有,從 /WEB-INF/lib/*.jar 加載。
  4. 若沒有,則依次從 System、Common、shared 加載(該步驟使用雙親委派)。

當然了,我們也可以通過配置來使 Webapp classLoader 嚴格按照雙親委派模式加載類:

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

嚴格雙親委派模式加載步驟:

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

--(完) --

看完本文有收穫?請轉發分享給更多人

關注「java程序媛之家」,提升Java技能

談談 Java 類加載機制

面試中常問的List去重問題,你都答對了嗎?

談談 Java 類加載機制

SpringBoot幾種定時任務的實現方式

談談 Java 類加載機制

32 歲程序員去面試,因年齡太大被直接送走。。

談談 Java 類加載機制

在 Java 中初始化 List 的五種方法

談談 Java 類加載機制


分享到:


相關文章: