提到 ClassLoader,最先想到的一定是“雙親委派”了,載入類時優先使用父類載入器(parent classloader),不過除了這個委託模型之外,還有很多細節值得研究
載入時機
除了顯示呼叫 ClassLoader。loadClass 進行載入 Class 之外,JVM 在下面的5種場景下,也會執行載入 Class 的操作(由 JVM 呼叫 ClassLoader。loadClassInternal)
使用 new 關鍵字例項化物件的時候、讀取或設定一個類的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候,以及呼叫一個類的靜態方法的時候。
使用 java。lang。reflect 包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含 main()方法的那個類),虛擬機器會先初始化這個主類。
當使用 JDK 1。7 的動態語言支援時,如果一個 java。lang。invoke。MethodHandle 例項最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
以上幾種載入時機,統稱為主動引用的方式;除此之外,其他引用類的方式都不會被觸發 Class 的載入
比如下面這種情況,就不屬於主動引用,不會進行類的載入
public class NotInitialization { public static void main(String[] args) { //根據場景1,讀取的是常量,不會造成類的初始化 System。out。println(ConstClass。HELLOWORLD); }}class ConstClass{ static final String HELLOWORLD = “hello world”; static { System。out。println(“ConstClass init!”); }}
Class。forName
透過 Class。forName 的形式,可以進行進行類的載入,不過這裡使用的是呼叫者(caller)的類載入器,也就是發起Class。forName 方法呼叫的類的類載入器
public static Class<?> forName(String className) throws ClassNotFoundException { //獲取caller Class Class<?> caller = Reflection。getCallerClass(); return forName0(className, true, ClassLoader。getClassLoader(caller), caller);}
類載入器 的“傳遞性”
首先說一下兩個概念,
定義載入器(defining loader)
和
初始載入器(initiating loader)
現有一個 ClassLoader A,如果透過A直接定義(而不是在A內委託 parent classLoader 載入)了一個 Class X,那麼稱 ClassLoader A 是 Class X 的
定義載入器
,也稱 ClassLoader A定義了 Class X
當一個 ClassLoader 委託 parent classLoader 進行載入某類(loadClass),那麼此時 loadClass 的 classLoader 和實際上 defineClass 的 parent classLoader 其實並不是同一個
現有一個 ClassLoader B,透過 ClassLoader B 的 loadClass 方法載入 Class Y,無論ClassLoader B 是直接 define了Class Y,還是委託 parent classLoader 去 define 了Class Y,那麼 ClassLoader B 都是 Class Y的
初始類載入器
。
只不過如果 Class Y 是由 ClassLoader B 直接載入的,那麼 Class Y的
定義載入器
和
初始載入器
都是ClassLoader B;如果是委託父類 parent classLoader C載入的,那麼
定義載入器
就是ClassLoader C,而
初始載入器
就是 ClassLoader B
如下圖所示,對於被載入的Class X來說,ClassLoader A就是
定義載入器
,而實際上 ClassLoader A是委託了 ClassLoader B 去完成 define 的,所以 ClassLoader B 是
定義載入器
使用 Class。forName 這種形式載入的 Class,實際上也是使用呼叫者(caller)的
定義載入器
這段雖然像繞口令一樣……但這也是 ClassLoader 的關鍵,ClassLoader 的傳遞特性非常重要
對於上面提到的
“主動引用”
方式的載入Class,在載入機制上有一些細節需要注意:
JVM在解釋 Class 時實際上 是
“惰性載入”
的,在解釋執行的行遇到引用後才會解析引用的 Class;比如在方法體中呼叫了某類,只有在執行到這一行時才會進行載入
對於同一個 ClassLoader 例項來說,在當前 Class 中沒有載入過的 Class,會使用
發起引用類的定義載入器(而不是 system ClassLoader)
進行載入
如果一個 ClassLoader 中已經載入過某個 Class,那麼就不會再載入(連 loadClass 都不會呼叫);比如 Class A裡引用了 Class X 和 Class B,Class B 裡也引用了 Class X,那麼在執行 Class B 時,不會再發生 loadClass(X) 的操作
根據上面幾個特點,自定義 ClassLoader 就比較簡單了,比如 Spring Boot 提供的可執行Jar(Executable Jar)的 ClassLoader 實現中:
只需要建立一個負責載入 jar 包內 jar 包的 ClassLoader(org。springframework。boot。loader。JarLauncher),然後入口類中透過該 ClassLoader 去載入我們程式碼中的 Main-Class 即可,這樣就可以做到載入 jar 包中的 jar 包了:
public void run() throws Exception { //透過前面設定的“負載載入jar包內jar包的ClassLoader”,去載入我們程式中的main類 Class<?> mainClass = Thread。currentThread()。getContextClassLoader() 。loadClass(this。mainClassName); Method mainMethod = mainClass。getDeclaredMethod(“main”, String[]。class); mainMethod。invoke(null, new Object[] { this。args });}
作者:空無
連結:https://juejin。cn/post/6950834844741926919