咱們常常會在面試中遇到有關類加載器的問題,而做爲一名Java開發人員應該瞭解類加載器如何工做?雙親委派模型是什麼?如何打破雙親委派?爲何打破?等等。因此今天的主題就是聊一聊類加載器。html
《深刻理解Java虛擬機》這本書你們都不陌生,想必咱們大多數人瞭解JVM知識都是經過這本書,在該書中也詳細介紹了Java類加載的全過程,包含加載、驗證、準備、解析和初始化這5個階段。java
在加載階段,經過一個類的全限定名來獲取此類的二進制字節流,就是依靠類加載器來完成。web
類加載器的一個做用就是將編譯器編譯生成的二進制 Class 文件加載到內存中,進而轉換成虛擬機中的類。Java系統提供了三種內置的類加載器:面試
固然,上面是 Java 默認的類加載器,咱們還能夠自定義類加載器,後文會分析如何自定義類加載器。spring
網上有文章分析說,類加載器遵循三個原則:委託性、可見性和惟一性原則。這三點其實都和雙親委派模型有關,雙親委派的工做過程以下:apache
當類加載器收到類的加載請求時,首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,全部的加載請求會傳送到頂層的啓動類加載器,只有父類加載器沒法完成加載請求,纔會交由子加載器去加載。後端
三個原則的具體體現是:數組
「委託性原則」 體如今當子類加載器收到類的加載請求時,會將加載請求向上委託給父類加載器。緩存
「可見性原則」 體如今容許子類加載器查看父類加載器加載的全部類,可是父類加載器不能查看子類加載器加載的類。tomcat
「惟一性原則」 體如今雙親委派整個機制保證了Java類的惟一性,假如你寫了一個和JRE核心類同名的類,好比Object類,雙親委派機制能夠避免自定義類覆蓋核心類的行爲,由於它首先會將加載類的請求,委託給ExtClassLoader去加載,ExtClassLoader再委託給BootstrapClassLoader,啓動類加載器若是發現已經加載了 Object類,那麼就不會加載自定義的Object類。
聊完雙親委派模型,你確定想知道它是如何實現,那麼來看一下 ClassLoader 的核心方法,其中的 loadClass 方法就是實現雙親委派機制的關鍵,爲了縮短代碼篇幅和方便閱讀,去掉了一些代碼細節:
package java.lang;
public abstract class ClassLoader {
protected Class defineClass(byte[] b);
protected Class<?> findClass(String name);
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 檢查類是否已經被加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//2. 委託給父類加載
c = parent.loadClass(name, false);
} else {
//3. 父類不存在的,交給啓動類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) { }
if (c == null) {
//4. 父類加載器沒法完成類加載請求時,調用自身的findClass方法來完成類加載
c = findClass(name);
}
}
return c;
}
}
複製代碼
如今咱們熟悉了 ClassLoader 的三個重要方法,那麼若是須要自定義一個類加載器的話,直接繼承 ClassLoader類,通常狀況只須要重寫 findClass 方法便可,本身定義加載類的路徑,能夠從文件系統或者網絡環境。
可是,若是想打破雙親委派機制,那麼還要重寫 loadClass 方法,只不過,爲何咱們要選擇去打破它呢? 咱們常使用的 Tomcat的類加載器就打破了雙親委派機制,固然還有一些其餘場景也打破了,好比涉及 SPI 的加載動做、熱部署等等。
接下來來看看 Tomcat 爲何打破雙親委派模型以及實現機制。
如今都流行使用 springboot 開發 web 應用,Tomcat 內嵌在 springboot 中。而在此以前,咱們會使用最原生的方式,servlet + Tomcat 的方式開發和部署 web 程序。web 應用的目錄結構大體以下:
| - MyWebApp
| - WEB-INF/web.xml -- 配置文件,用來配置Servlet等
| - WEB-INF/lib/ -- 存放Web應用所需各類JAR包
| - WEB-INF/classes/ -- 存放你的應用類,好比Servlet類
| - META-INF/ -- 目錄存放工程的一些信息
複製代碼
一個 Tomcat 可能會部署多個這樣的 web 應用,不一樣的 web 應用可能會依賴同一個第三方庫的不一樣版本,爲了保證每一個 web 應用的類庫都是獨立的,須要實現類隔離。而Tomcat 的自定義類加載器 WebAppClassLoader 解決了這個問題,每個 web 應用都會對應一個 WebAppClassLoader 實例,不一樣的類加載器實例加載的類是不一樣的,Web應用之間通各自的類加載器相互隔離。
固然 Tomcat自定義類加載器不僅解決上面的問題,WebAppClassLoader 打破了雙親委派機制,即它首先本身嘗試去加載某個類,若是找不到再代理給父類加載器,其目的是優先加載Web應用定義的類。
WebappClassLoader 具體實現機制是重寫了 ClassLoader 的 findClass 和 loadClass 方法。
public Class<?> findClass(String name) throws ClassNotFoundException {
...
Class<?> clazz = null;
try {
//1. 先在 Web 應用目錄下查找類
clazz = findClassInternal(name);
} catch (RuntimeException e) {
throw e;
}
if (clazz == null) {
try {
//2. 若是在本地目錄沒有找到,交給父加載器去查找
clazz = super.findClass(name);
} catch (RuntimeException e) {
throw e;
}
}
//3. 若是父類也沒找到,拋出 ClassNotFoundException
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
複製代碼
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
//1. 先在本地緩存查找該類是否已經加載過
clazz = findLoadedClass0(name);
if (clazz != null) {
return clazz;
}
//2. 從系統類加載器的緩存中查找是否加載過
clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
//3. 嘗試用 ExtClassLoader 類加載器類加載
ClassLoader javaseLoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 4. 嘗試在本地目錄搜索 class 並加載
try {
clazz = findClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 5. 嘗試用系統類加載器 (也就是 AppClassLoader) 來加載
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// 省略
}
}
//6. 上述過程都加載失敗,拋出 ClassNotFoundException 異常
throw new ClassNotFoundException(name);
}
複製代碼
從上面的代碼中能夠看到,Tomcat 自定義的類加載器確實打破了雙親委派機制,同時根據 loadClass 方法的核心邏輯,我也畫了一張圖,描述了默認狀況下 Tomcat 的類加載機制。
一開始將類加載請求委託給 ExtClassLoader,而不是委託給 AppClassLoader,這樣的緣由是 防止 web 應用本身的類覆蓋JRE的核心類,若是 JRE 核心類中沒有該類,那麼才交給自定義的類加載器 WebappClassLoader 去加載。
這篇文章主要總結了類加載器的雙親委派模型、雙親委派的工做機制、以及Tomcat如何打破雙親委派,固然有一些東西分享的比較簡單,好比 Tomcat 的類加載器這部分,沒有說起整個 Tomcat的類加載器層次結構,沒有提到 SharedClassLoader 和 CommonClassLoader 類加載器,這個等後續有時間再來分享。
同時,歡迎關注我新開的公衆號,按期分享Java後端知識!