什么是類加載器?
類加載器是JVM的核心組件之一,負責將Java字節碼文件(.class文件)加載到JVM內存中。由于JVM只能執行二進制字節碼,類加載器的作用就是將編譯后的.class文件轉換為JVM可以理解和執行的格式,使Java程序能夠正常啟動運行。
類加載器的分類
JVM中有四種類型的類加載器,形成層次結構:
- 啟動類加載器(Bootstrap ClassLoader):負責加載Java核心類庫(如java.lang.*等),由C++實現
- 擴展類加載器(Extension ClassLoader):負責加載JRE擴展目錄中的類庫
- 應用程序類加載器(Application ClassLoader):負責加載用戶類路徑(classpath)上的類
- 自定義類加載器(Custom ClassLoader):用戶自定義的類加載器,繼承自ClassLoader類
雙親委派模型
雙親委派模型是類加載器的核心工作機制。當一個類加載器接收到類加載請求時,它首先不會自己嘗試加載這個類,而是將請求委派給父類加載器。只有當父類加載器無法完成加載時,子類加載器才會嘗試自己加載。
采用雙親委派機制的原因:
- 保證類的唯一性:避免同一個類被不同的類加載器重復加載,確保JVM中每個類只有一個Class對象
- 保證安全性:防止核心Java類庫被惡意替換或修改,維護Java運行環境的安全性
類加載過程
類的生命周期包含7個階段:加載 → 驗證 → 準備 → 解析 → 初始化 → 使用 → 卸載
其中驗證、準備、解析統稱為連接(Linking) 階段。
各階段詳解:
- 加載(Loading):查找并導入.class文件,將字節碼數據讀入內存
- 驗證(Verification):確保加載的類符合JVM規范,檢查字節碼的正確性和安全性
- 準備(Preparation):為類的靜態變量分配內存空間,并設置默認初始值(如int類型設為0)
- 解析(Resolution):將類中的符號引用轉換為直接引用,建立實際的內存地址映射
- 初始化(Initialization):執行類的靜態代碼塊和靜態變量的賦值操作
- 使用(Using):JVM開始執行程序的入口方法,正式運行用戶代碼
- 卸載(Unloading):程序執行完畢后,JVM銷毀不再使用的Class對象,釋放內存
面試重點
常見面試問題及答案:
1. 什么情況下會觸發類的初始化?
- 主動引用(會觸發初始化):
- 創建類的實例(new操作)
- 訪問類的靜態變量或靜態方法
- 通過反射調用類
- 初始化子類時,父類會先初始化
- JVM啟動時的主類
- 被動引用(不會觸發初始化):
- 通過子類引用父類的靜態字段
- 定義類數組
- 引用常量(final static)
2. 如何自定義類加載器?
- 繼承ClassLoader類,重寫
findClass()
方法 - 在
findClass()
中調用defineClass()
方法將字節碼轉換為Class對象 - 可以重寫
loadClass()
方法來打破雙親委派機制
public class MyClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 獲取字節碼數據byte[] classData = getClassData(name);// 調用defineClass轉換為Class對象return defineClass(name, classData, 0, classData.length);}
}
3. 雙親委派模型有什么缺點?如何打破雙親委派?
- 缺點:父類加載器無法訪問子類加載器加載的類,導致某些場景下的類加載問題
- 打破方式:
- 重寫
loadClass()
方法而不調用父類的loadClass()
- 使用線程上下文類加載器(Thread Context ClassLoader)
- 使用OSGi框架的網狀類加載器結構
- 重寫
4. 類加載器之間的關系是繼承還是組合?
- 組合關系:每個類加載器都持有一個parent引用指向父類加載器
- 不是繼承關系,而是通過組合實現委派機制
- 除了啟動類加載器,所有類加載器都有父類加載器
關鍵記憶點:
- 類加載器使用組合而非繼承關系
- 雙親委派保證了Java核心類庫的統一性和安全性
- 類加載過程是懶加載的,只有在需要時才會加載
Class.forName()
會觸發類初始化,而ClassLoader.loadClass()
不會