Java 類加載機制雙親委派與自定義類加載器

我們來深入解析 Java 類加載機制。這是理解 Java 應用如何運行、如何實現插件化、以及解決一些依賴沖突問題的關鍵。

一、核心概念:類加載過程

一個類型(包括類和接口)從被加載到虛擬機內存開始,到卸載出內存為止,它的整個生命周期會經歷加載(Loading)、連接(Linking)、初始化(Initialization)、使用(Using)和卸載(Unloading)?五個階段。其中連接階段又分為驗證(Verification)、準備(Preparation)、解析(Resolution)?三步。

  • 加載: 查找并加載類的二進制字節流(如?.class?文件),并為其在方法區創建一個?java.lang.Class?對象作為訪問入口。

  • 驗證: 確保被加載的類的正確性和安全性,如文件格式、元數據、字節碼、符號引用驗證。

  • 準備: 為類變量(static 變量)?分配內存并設置默認初始值(零值)。例如?public static int value = 123;?在此階段后?value?為?0

  • 解析: 將常量池內的符號引用替換為直接引用的過程。

  • 初始化: 執行類構造器?<clinit>()?方法的過程,該方法由編譯器自動收集類中所有類變量的賦值動作靜態語句塊(static{}塊)?中的語句合并產生。此時?value?才會被賦值為?123

重要原則:《Java虛擬機規范》嚴格規定了有且只有以下六種情況必須立即對類進行“初始化”:

  1. 遇到?new,?getstatic,?putstatic,?invokestatic?這四條字節碼指令時。

  2. 使用?java.lang.reflect?包的方法對類進行反射調用時。

  3. 當初始化一個類時,發現其父類還未初始化,需先觸發其父類的初始化。

  4. 虛擬機啟動時,用戶指定的主類(包含?main()?方法的那個類)。

  5. 當使用 JDK 7 新加入的動態語言支持時...

  6. 當一個接口中定義了 JDK 8 新加入的默認方法(default方法)時...


二、雙親委派模型 (Parents Delegation Model)

Java 虛擬機如何決定由哪個類加載器來加載一個類?答案就是雙親委派模型。

1. 類加載器層次結構

JVM 提供了三層類加載器:

  • 啟動類加載器 (Bootstrap ClassLoader)

    • C++ 實現,是 JVM 自身的一部分。

    • 負責加載?<JAVA_HOME>/lib?目錄下的核心類庫(如?rt.jar,?charsets.jar)或被?-Xbootclasspath?參數指定的路徑中的類。

    • 所有類加載器的父加載器

  • 擴展類加載器 (Extension ClassLoader)

    • Java 實現,繼承自?ClassLoader

    • 負責加載?<JAVA_HOME>/lib/ext?目錄下,或由?java.ext.dirs?系統變量指定的路徑中的所有類庫。

  • 應用程序類加載器 (Application ClassLoader)

    • Java 實現,繼承自?ClassLoader

    • 也叫系統類加載器 (System ClassLoader)

    • 負責加載用戶類路徑 (ClassPath)?上的所有類庫。

    • 是程序中默認的類加載器ClassLoader.getSystemClassLoader()?返回的就是它。

除了這三個,用戶還可以自定義類加載器。

2. 雙親委派的工作流程

當一個類加載器收到了類加載請求時,它不會自己去嘗試加載,而是會把這個請求委托給父類加載器去完成。每一層的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中。只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

流程可以概括為:自底向上檢查類是否已加載 -> 自頂向下嘗試加載類。

圖表

3. 雙親委派的優點
  1. 避免類的重復加載: 確保一個類在 JVM 中全局唯一。無論哪一個類加載器要加載這個類,最終都是委派給最頂層的啟動類加載器去加載,從而保證了類的唯一性。

  2. 保證核心 API 的安全: 防止用戶自定義一個核心類(例如?java.lang.String)來替換掉 Java 核心庫中的類,從而確保了 Java 核心庫的安全性和穩定性。例如,即使用戶寫了一個?java.lang.Object?類并放在 ClassPath 中,最終也只會由 Bootstrap ClassLoader 去加載核心庫中的?Object?類,用戶的類不會被加載。


三、打破雙親委派模型

雙親委派模型不是一個強制性的約束,而是 Java 設計者推薦給開發者的一種類加載器實現方式。在某些特定場景下,需要打破這個模型。

1. 歷史原因:自身的缺陷

雙親委派模型很好地解決了各個類加載器協作時基礎類型的一致性問題(越基礎的類由越上層的加載器進行加載)。但如果基礎類型要調用回用戶的代碼,雙親委派模型就無法解決了。一個典型的例子就是?JNDI(Java Naming and Directory Interface),它的代碼由啟動類加載器加載,但需要調用由獨立廠商實現并部署在應用程序 ClassPath 下的 JNDI 服務提供者接口(SPI)的代碼。啟動類加載器不可能“認識”這些用戶代碼

2. 解決方案:線程上下文類加載器 (Thread Context ClassLoader)

為了解決這個問題,Java 引入了線程上下文類加載器。這個類加載器可以通過?java.lang.Thread.setContextClassLoader()?方法進行設置,如果創建線程時未設置,它將會從父線程中繼承一個;如果在應用程序的全局范圍內都沒有設置過,那默認就是應用程序類加載器。

SPI(Service Provider Interface)機制(如 JDBC)就利用了這個方案:

  1. java.sql.DriverManager(由 Bootstrap ClassLoader 加載)在初始化時,會通過?ServiceLoader?去加載?java.sql.Driver?接口的實現類。

  2. ServiceLoader?的?load?方法會使用線程上下文類加載器(默認是 AppClassLoader)去加載 META-INF/services 目錄下的配置文件中的實現類。

  3. 這樣,位于 ClassPath 下的第三方數據庫驅動包(如?mysql-connector-java.jar)中的實現類(如?com.mysql.cj.jdbc.Driver)就能被成功加載并注冊了。

這個過程可以看作是:父類加載器(Bootstrap)請求子類加載器(AppClassLoader)來完成類加載動作,這種行為實際上打破了雙親委派模型的層次結構,是一種逆向的委托。

3. 其他打破雙親委派的場景
  • 熱部署、熱替換: 例如 OSGi、JSP 應用服務器(如 Tomcat)。每個模塊(Bundle)都有自己的類加載器,當需要更換一個 Bundle 時,就把 Bundle 連同類加載器一起換掉,從而實現代碼的熱替換。

  • 實現應用隔離: 例如 Tomcat 為每個 Web 應用創建一個獨立的?WebappClassLoader,優先加載?/WEB-INF/?下的類,應用之間互不干擾,這同樣打破了雙親委派(它沒有先委托給父加載器,而是自己先嘗試加載)。


四、自定義類加載器

1. 為什么要自定義?
  • 從非標準來源加載類(如網絡、加密的字節流)。

  • 實現類的隔離和熱部署。

  • 修改類的加載方式(如打破雙親委派)。

2. 如何實現?

通常不直接重寫?loadClass()?方法(因為該方法實現了雙親委派邏輯),而是重寫?findClass(String name)?方法

步驟

  1. 繼承?java.lang.ClassLoader

  2. 重寫?findClass?方法:

    • 在這個方法中,根據指定的類名(如?com.example.MyClass)去查找類的字節碼(byte[])。

    • 找到后,調用父類的?defineClass(byte[] b, int off, int len)?方法,將字節數組轉換為?Class?對象。

  3. (可選)如果要打破雙親委派,可以重寫?loadClass?方法,實現自己的加載邏輯。

示例代碼框架

java

public class MyClassLoader extends ClassLoader {private String classPath; // 自定義的類查找路徑@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 1. 根據 name 和 classPath,找到 .class 文件,讀取為 byte[]byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {// 2. 調用 defineClass,將字節碼定義為 Class 對象return defineClass(name, classData, 0, classData.length);}}private byte[] loadClassData(String name) {// 實現從自定義路徑(文件、網絡等)加載類的字節碼的邏輯// 將包名中的 '.' 替換為路徑分隔符 '/'String path = name.replace('.', '/').concat(".class");path = classPath + "/" + path;// ... 讀取文件并返回 byte[]return data;}
}

總結

概念核心要點
雙親委派模型保證核心類安全、避免重復加載。工作流程:子加載器委托父加載器加載,父加載器無法完成時子加載器才自己加載。
打破雙親委派SPI 等場景需要父加載器請求子加載器完成加載。通過線程上下文類加載器實現,是 Java 生態擴展性的重要基礎。
自定義類加載器重寫?findClass?方法,從特定來源獲取字節碼后調用?defineClass。常用于加載非標準來源的類、實現隔離和熱部署。

理解類加載機制,尤其是雙親委派和打破它的場景,對于解決日常開發中的 ClassNotFoundException、NoClassDefFoundError、依賴沖突以及構建模塊化系統都至關重要。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96340.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96340.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96340.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kaggle項目實踐——Titanic: Machine Learning from Disaster

泰坦尼克號沉船事件是機器學習領域最經典的入門項目之一。Kaggle 上的 Titanic: Machine Learning from Disaster 競賽&#xff0c;被無數人稱為“機器學習的 Hello World”。 一、數據導入與清洗&#xff1a;讓數據從 “雜亂” 變 “干凈” 機器學習模型就像 “挑食的孩子”…

Qt C++ 復雜界面處理:巧用覆蓋層突破復雜界面處理難題?之二

接上一篇&#xff0c;繼續探索“覆蓋層”的使用方法。 五、覆蓋層進階交互&#xff1a;從 “能繪制” 到 “好操作”? 基礎的繪制功能只能滿足 “看得見” 的需求&#xff0c;實際開發中還需要 “能操作”—— 比如選中線條修改顏色、按 Delete 鍵刪除線條、鼠標 hover 時高亮…

神經網絡構成框架-理論學習

一、神經網絡的基本組成與分類 1.1 神經網絡的核心組成部分 神經網絡是現代人工智能的基石&#xff0c;其設計靈感來源于生物神經系統的信息處理方式。作為工程師&#xff0c;了解神經網絡的基本組成部分對于構建和優化模型至關重要。一個典型的神經網絡主要由以下幾個關鍵部分…

從0開始開發app(AI助手版)-架構及環境搭建

架構選擇 前端React Native 后端Firebase 原因 環境準備 安裝node 安裝JDK 命令行工具&#xff1a;Node.js command prompt命令行查詢Javav版本&#xff1a;javac -version使用nrm工具切換淘寶源&#xff1a;npx nrm use taobao安裝yarn&#xff0c;替代npm下載工具&#x…

【性能測試】Jmeter工具快速上手-搭建壓力測試腳本

&#x1f525;個人主頁&#xff1a; 中草藥 &#x1f525;專欄&#xff1a;【Java】登神長階 史詩般的Java成神之路 概念 性能測試是軟件測試的重要分支&#xff0c;核心目標是通過模擬真實業務場景和負載壓力&#xff0c;評估系統在不同條件下的性能表現&#xff0c;發現系統性…

oracle里的int類型

oracle里的int類型 在 ANSI SQL 標準 中&#xff0c;INTEGER 和 SMALLINT 是定義好的精確數值類型&#xff0c;但它們的 “長度”或“大小”并不是通過 (N) 括號來指定的&#xff08;如 INT(4)&#xff09;&#xff0c;這一點與 MySQL 等數據庫的非標準擴展完全不同。 SMALLI…

前端學習之后端java小白(二)-sql約束/建表

一、約束SQL約束&#xff08;Constraints&#xff09;是用于限制表中數據的規則&#xff0c;確保數據的完整性和準確性。以下是主要的SQL約束類型&#xff1a; 主要約束類型&#xff1a; 1. NOT NULL 約束: 確保列不能包含空值 CREATE TABLE users (id INT NOT NULL,name VARC…

OpenCV:圖像金字塔

文章目錄一、什么是圖像金字塔&#xff1f;二、圖像金字塔的核心操作&#xff1a;采樣與逆采樣1. 向下采樣&#xff08;pyrDown&#xff09;&#xff1a;從高分辨率到低分辨率步驟1&#xff1a;高斯濾波步驟2&#xff1a;刪除偶數行與偶數列OpenCV實戰代碼效果特點2. 向上采樣&…

LVS與Keepalived詳解(一)負載均衡集群介紹

文章目錄前言一、什么是LVS&#xff1f;二、四層&#xff08;L4&#xff09;負載均衡的最佳解決方案是什么&#xff1f;2.1解決方案分類與對比&#xff08;負載均衡的三種方式介紹&#xff09;2.1.1 硬件負載均衡 (Hardware Load Balancer)2.1.2 軟件負載均衡 (Software Load B…

消息隊列-kafka完結

基本概念和操作 基本概念 簡單概念:::color4 Broker&#xff1a;如果將kafka比喻成數據倉庫網絡&#xff0c;那么Broker就是網絡中的倉庫節點&#xff0c;比如快遞站&#xff0c;在該節點內可以獨立運行&#xff0c;并且多個Broker可以連接起來&#xff0c;構建kafka集群Topic&…

Chromium 138 編譯指南 Windows篇:環境變量配置與構建優化(三)

引言配置&#xff0c;往往決定成敗。在軟件開發的世界里&#xff0c;環境變量就像是一位無聲的指揮家&#xff0c;默默地協調著各個組件的協同工作。對于Chromium 138這樣一個擁有數千萬行代碼的超大型項目而言&#xff0c;正確的環境變量配置更是編譯成功的關鍵所在。也許您曾…

LabVIEW加載 STL 模型至 3D 場景 源碼見附件

LabVIEW 中 STL 模型的導入與 3D 場景顯示&#xff0c;基于示例代碼邏輯&#xff0c;結合格式兼容性、功能實現步驟及多樣化顯示方式&#xff0c;適用于三維可視化溫控、機械零件模擬等場景。 1示例代碼 NI 社區案例 “Add an STL file to 3D scene using LabVIEW” 提供了經…

硅基計劃3.0 Map類Set類

文章目錄一、二叉搜索樹&#xff08;排序樹&#xff09;1. 概念初識2. 模擬實現1. 創建搜索樹節點2. 查找指定元素是否存在3. 插入4. 刪除二、Map類1. put——設置單詞以及其頻次2. get——獲取單詞頻次3. getOrDefault——獲取單詞頻次或返回默認值4. remove——刪除單詞頻次信…

LeetCode 刷題【73. 矩陣置零】

73. 矩陣置零 自己做 解&#xff1a;標記消除 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {vector<bool> x(matrix.size(), false); //要置0的行vector<bool> y(matrix[0].size(), false); //…

Unity學習----【進階】TextMeshPro學習(一)--基礎知識點

來源于唐老獅的視頻教學&#xff0c;僅作記錄和感悟記錄&#xff0c;方便日后復習或者查找 一.導入TextMeshPro 對于新創建的工程&#xff0c;可以直接在這里導入TMP必要的資源&#xff08;上面&#xff09;&#xff0c;以及TMP的實例和擴展&#xff08;下面&#xff09; 導入之…

BigDecimal(用于處理超出double范圍的浮點數)

BigDecimal 是 Java 中 java.math 包提供的高精度十進制浮點數類&#xff0c;專為解決基本類型&#xff08;float/double&#xff09;的精度缺陷而設計&#xff0c;廣泛用于金融、科學計算等對精度要求極高的場景。以下從核心特性、使用方法、常見問題對比、注意事項等方面詳細…

Nginx 優化

文章目錄1、隱藏版本號2、修改用戶與組3、緩存時間4、日志切割5、連接超時6、更改進程數7、配置網頁8、防盜鏈1、隱藏版本號 隱藏nginx的版本號&#xff0c;為了防止惡意用戶利用已知漏洞進行攻擊 ## 查看版本號 curl -I http://192.168.10.23方法一&#xff1a;修改配置文件…

基于多模態與主動學習的車船飛機圖像識別系統研究與應用技術方案

技術方案 一、技術背景與研究現狀 圖像識別是計算機視覺的核心任務之一&#xff0c;隨著深度學習的發展&#xff0c;基于 卷積神經網絡&#xff08;CNN&#xff09; 與 視覺Transformer&#xff08;ViT&#xff09; 的圖像分類方法已成為主流。 根據《圖像分類技術選型——截止…

Word2Vec詞嵌入技術和動態詞嵌入技術

Word2Vec&#xff08;Word to Vector&#xff09;是 2013 年由 Google 團隊提出的無監督詞嵌入模型&#xff0c;是一種靜態詞嵌入技術&#xff0c;核心目標是將自然語言中的離散詞匯映射為低維、稠密的實數向量&#xff08;即 “詞向量”&#xff09;&#xff0c;讓向量空間的距…

Netty從0到1系列之Netty邏輯架構【上】

文章目錄一、Netty邏輯架構【上】1.1 網絡通信層1.1.1 BootStrap & ServerBootStrap1. ?核心方法鏈與配置2. ? 架構與流程3. ? 底層實現與原理分析4. ? 實踐經驗與總結1.1.2 Channel1.2 事件調度層1.2.1 事件調度層概述1.2.2 EventLoop【事件循環】1.2.3 EventLoopGrou…