【JVM】Java類加載機制

【JVM】Java類加載機制

什么是類加載?

在 Java 的世界里,每一個類或接口在經過編譯后,都會生成對應的 .class 字節碼文件。

所謂類加載機制,就是 JVM 將這些 .class 文件中的二進制數據加載到內存中,并對其進行校驗、解析、初始化等一系列操作。最終,每個類都會在方法區(或元空間)中保留一份結構化的類信息(元數據),并在Java 堆中創建一個 java.lang.Class 類型的對象,供程序運行時使用。

從 JVM 的角度看,一個類的生命周期包括以下 7 個階段:

加載 → 驗證 → 準備 → 解析 → 初始化 → 使用 → 卸載

其中,前五個階段(加載、驗證、準備、解析、初始化)統稱為類的加載過程。其中,驗證、準備和解析這三個階段可以統稱為連接
請添加圖片描述

需要注意的是,類加載的五個階段并不是嚴格按順序線性執行的,而是相互交叉、動態混合的過程

例如:

  • 部分驗證操作(如文件格式驗證)可能在加載 .class 文件的過程中就被觸發。
  • 符號引用的解析可能被延遲到真正使用時才發生。

類加載的詳細過程

1.加載

  • 任務: 查找并加載類的二進制數據(通常是 .class 文件,但來源可以多樣)。

  • 過程:

    • 通過類的全限定名(如 java.lang.Stringcom.example.MyClass)獲取定義此類的二進制字節流。這個字節流來源可以是文件系統(最常見的)、網絡、ZIP/JAR包、運行時計算生成(動態代理)、數據庫等等。

    類的全限定名就是類的完整名稱,即:包名 + 類名(如 java.lang.Stringcom.example.MyClass

    • 將這個字節流所代表的靜態存儲結構轉換為方法區 的運行時數據結構。
    • 堆(Heap) 內存中創建一個代表這個類的 java.lang.Class 對象,作為方法區中這個類的各種數據的訪問入口。常用的 .classgetClass() 返回的就是這個對象。
    public class CustomLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) {// 1. 獲取字節流(可來自文件/網絡/數據庫等)byte[] bytes = loadClassBytes(name); // 2. 轉換為方法區數據結構// 3. 創建Class對象return defineClass(name, bytes, 0, bytes.length);}
    }
    
  • 關鍵點:

    • 類加載器 完成。

    • 加載的最終產物是堆中的 Class 對象。

    • 類的加載是懶惰的,首次用到時才會加載:

      1. 使用了類.class
      2. 用類加載器的 loadClass 方法加載類
      3. 滿足類的初始化條件(后文有詳細介紹)

      參考:2-類加載_驗證類加載是懶惰的_嗶哩嗶哩_bilibili

2.連接

2.1 驗證
  • 任務: 確保加載的字節碼信息符合 JVM 規范,是安全、無害的,不會危害虛擬機自身安全。
  • 內容:
    • 文件格式驗證(魔數、版本號等)
    • 元數據驗證(語義分析,如是否有父類、是否繼承了final類、是否實現接口所有方法等)
    • 字節碼驗證(數據流和控制流分析,確保邏輯正確,如操作數棧類型匹配、跳轉指令目標合理等)
    • 符號引用驗證(檢查常量池中的符號引用能否找到對應的類、字段、方法等)。
  • 重要性: 保護JVM安全,防止惡意代碼或損壞的字節碼文件導致JVM崩潰或執行危險操作。雖然耗點時間,但對系統穩定性至關重要。
2.2 準備
  • 任務: 為類的靜態變量分配內存,并設置默認初始值
  • 過程:
    • 在方法區中為這些靜態變量分配內存空間。
    • 設置默認初始值:
      • 基本類型:int -> 0, long -> 0L, float -> 0.0f, double -> 0.0d, char -> '\u0000', boolean -> false
      • 引用類型:null
  • 關鍵點:
    • 這里分配內存并初始化的是類變量(static變量),不是實例變量
    • 初始化的值是默認零值,不是代碼中顯式賦的值(如 public static int value = 123;,在準備階段后 value0,賦值 123 的操作發生在后面的初始化階段)。
    • 如果靜態變量是 final 修飾的基本類型或 String 常量,并且在編譯時就能確定值(如 public static final int CONSTANT = 100;),那么這個值會直接在準備階段被賦予(此時 CONSTANT 就是 100)。
2.3 解析
  • 任務: 將常量池內的符號引用 替換為直接引用
  • 符號引用與直接引用:
    • 符號引用: 一組描述被引用目標(類、字段、方法)的符號。例如,java/lang/Object(類名)、toString:()Ljava/lang/String;(方法名和描述符)。它只是一個字面量引用,與內存布局無關。
    • 直接引用: 一個能直接定位到目標(類在方法區的地址、字段或方法在內存中的偏移量或句柄)的指針、偏移量或句柄。它是與JVM運行時內存布局相關的。
  • 過程: JVM 查找符號引用所指向的類、字段或方法的實際位置,并將常量池中的符號引用替換為指向該位置的直接引用。
  • 時機: 解析階段可以在初始化之前完成,也可以在初始化之后完成(甚至延遲到第一次實際使用該符號引用時),這取決于 JVM 的實現策略(“及早解析”或“惰性解析”)。

3.初始化

  • 任務: 執行類的初始化代碼,主要是執行類構造器 <clinit>() 方法。
  • <clinit>() 方法:
    • 由編譯器自動收集類中所有類變量(static變量)的顯式賦值動作靜態代碼塊(static {} 塊) 中的語句合并生成。
    • 順序:按源代碼中出現的順序執行。
    • 父類的 <clinit>() 優先于子類的執行。
    • 虛擬機會保證一個類的 <clinit>() 方法在多線程環境下被正確地加鎖、同步(即線程安全)。如果一個線程正在執行它,其他線程會阻塞等待。
  • 觸發時機(嚴格規定): 類只有在以下 6 種情況之一發生時,才會立即進行初始化(加載和連接可能更早發生):
    1. 創建類的實例 (new)。
    2. 訪問類的靜態變量(讀取或賦值),除非該靜態變量是 final 常量并且在編譯期就能確定值(常量傳播優化)。
    3. 調用類的靜態方法 (static 方法)。
    4. 使用反射 (Class.forName("..."), getMethod 等) 對類進行反射調用。
    5. 初始化一個類的子類時,會觸發其父類的初始化。
    6. JVM 啟動時被標明為啟動類(包含 main() 方法的那個類)。
  • 關鍵點:
    • 這是類加載過程的最后一步
    • 此時才真正執行程序員的代碼邏輯(靜態賦值、靜態塊)。
    • 之前的“準備”階段只是分配內存并賦零值,這里是賦程序員定義的值。

類加載器

在類加載的第一個階段——加載中,JVM 需要根據類的全限定名,找到并讀取其對應的字節碼文件(.class 文件)。

這個查找和讀取 .class 字節流的工作,正是由類加載器來完成的。

請添加圖片描述

JVM 中內置了三個重要的 ClassLoader

  1. Bootstrap ClassLoader (啟動類/引導類加載器):
    • 用原生代碼(C/C++)實現,是 JVM 自身的一部分。
    • 負責加載 JAVA_HOME/lib 目錄下的核心 Java 庫(如 rt.jar, charsets.jar)或 -Xbootclasspath 參數指定的路徑中的類。
    • 是最高級別的加載器,沒有父加載器
    • null 表示: 在 Java 代碼中試圖獲取它的引用時,返回 null
  2. Extension ClassLoader (擴展類加載器):
    • sun.misc.Launcher$ExtClassLoader 實現(Java)。
    • 負責加載 JAVA_HOME/lib/ext 目錄下的擴展庫,或 java.ext.dirs 系統變量指定的路徑中的所有類庫。
    • 父加載器是 Bootstrap ClassLoader
  3. Application ClassLoader (應用程序類加載器 / 系統類加載器):
    • sun.misc.Launcher$AppClassLoader 實現(Java)。
    • 負責加載用戶類路徑(ClassPath) 上所指定的類庫。這是我們程序中默認的類加載器。
    • 父加載器是 Extension ClassLoader
    • 通過 ClassLoader.getSystemClassLoader() 可以獲取到它。

除了這三種類加載器之外,用戶還可以加入自定義的類加載器來進行拓展,以滿足自己的特殊需求:

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的類加載器都是在 JVM 外部實現的,并且全都繼承自 ClassLoader抽象類。如果我們要自定義自己的類加載器,需要繼承 ClassLoader抽象類。

ClassLoader 類中有兩個核心方法:

  • protected Class<?> loadClass(String name, boolean resolve)

    加載指定名稱的類。該方法實現了雙親委派模型:會先委托給父加載器嘗試加載,如果父加載器無法完成,才會調用自身的 findClass() 方法進行加載。

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 先檢查當前類是否已經加載Class<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {// 先讓父類加載器嘗試加載c = parent.loadClass(name, false);} else {// 如果沒有父加載器(即引導類加載器),使用 bootstrap 加載c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 忽略異常,進入下一步由自己加載}if (c == null) {// 如果父加載器無法加載,再嘗試使用當前類加載器加載c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}
    }
    
  • protected Class<?> findClass(String name)
    根據類名查找類的定義并返回對應的 Class 對象。默認實現是拋出 ClassNotFoundException,需要我們在子類中重寫。

    protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);
    }
    

如果我們不想打破雙親委派模型,就重寫 ClassLoader 類中的 findClass() 方法即可,無法被父類加載器加載的類最終會通過這個方法被加載。但是,如果想打破雙親委派模型則需要重寫 loadClass() 方法。

雙親委派模型簡介

雙親委派模型是 Java 類加載機制的重要組成部分,它通過委派父加載器優先加載類的方式,實現了兩個關鍵的安全目標:避免類的重復加載和防止核心 API 被篡改。

  • 工作流程: 當一個類加載器收到加載類的請求時:

    1. 它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成。
    2. 每一層的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器
    3. 只有當父加載器反饋自己無法完成這個加載請求(在它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載
  • 核心思想:向上委派,向下加載”。

    請添加圖片描述

  • 核心目的:

    • 保證基礎類的唯一性和安全性: 防止用戶自定義一個與核心庫(如 java.lang.Object)同名的類被加載,從而覆蓋核心庫的行為(沙箱安全機制)。
    • 避免重復加載: 父加載器已經加載過的類,子加載器就不會再加載(在同一個命名空間內)。

內容參考

【JVM】Java類加載機制這塊算是玩明白了_嗶哩嗶哩_bilibili

類加載器詳解(重點)

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

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

相關文章

vue的監聽屬性watch的詳解

文章目錄 1. 監聽屬性 watch2. 常規用法3. 監聽對象和route變化4. 使用場景 1. 監聽屬性 watch watch 是一個對象&#xff0c;鍵是需要觀察的表達式&#xff0c;用于觀察 Vue 實例上的一個表達式或者一個函數計算結果的變化。回調函數的參數是新值和舊值。值也可以是方法名&am…

如何在 Ubuntu 24.04 服務器上安裝 Apache Solr

Apache Solr 是一個免費、開源的搜索平臺&#xff0c;廣泛應用于實時索引。其強大的可擴展性和容錯能力使其在高流量互聯網場景下表現優異。 Solr 基于 Java 開發&#xff0c;提供了分布式索引、復制、負載均衡及自動故障轉移和恢復等功能。 本教程將指導您如何在 Ubuntu 24.…

Linux內核中TCP三次握手的實現機制詳解

TCP三次握手是建立可靠網絡連接的核心過程,其在內核中的實現涉及復雜的協議棧協作。本文將深入分析Linux內核中三次握手的實現機制,涵蓋客戶端與服務端的分工、關鍵函數調用、協議號驗證及數據包處理流程。 一、三次握手的整體流程 三次握手分為三個階段,客戶端與服務端通過…

服務器--寶塔命令

一、寶塔面板安裝命令 ?? 必須使用 root 用戶 或 sudo 權限執行&#xff01; sudo su - 1. CentOS 系統&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系統…

優化 Spring Boot API 性能:利用 GZIP 壓縮處理大型有效載荷

引言 在構建需要處理和傳輸大量數據的API服務時&#xff0c;響應時間是一個關鍵的性能指標。一個常見的場景是&#xff0c;即使后端邏輯和數據庫查詢已得到充分優化&#xff0c;當API端點返回大型數據集&#xff08;例如&#xff0c;數千條記錄的列表&#xff09;時&#xff0…

【WPF】WPF 項目實戰:構建一個可增刪、排序的光源類型管理界面(含源碼)

&#x1f4a1;WPF 項目實戰&#xff1a;構建一個可增刪、排序的光源類型管理界面&#xff08;含源碼&#xff09; 在實際的圖像處理項目中&#xff0c;我們經常需要對“光源類型”進行篩選或管理。今天我們來一步步構建一個實用的 WPF 界面&#xff0c;實現以下功能&#xff1…

C++23 已棄用特性

文章目錄 1. std::aligned_storage 與 std::aligned_union1.1 特性介紹1.2 被棄用的原因1.3 替代方案 2. std::numeric_limits::has_denorm2.1 特性介紹2.2 被棄用的原因 3. 總結 C23 已棄用特性包括&#xff1a;std::aligned_storage、std::aligned_union 與 std::numeric_lim…

十三、【核心功能篇】測試計劃管理:組織和編排測試用例

【核心功能篇】測試計劃管理:組織和編排測試用例 前言準備工作第一部分:后端實現 (Django)1. 定義 `TestPlan` 模型2. 生成并應用數據庫遷移3. 創建 `TestPlanSerializer`4. 創建 `TestPlanViewSet`5. 注冊路由6. 注冊到 Django Admin第二部分:前端實現 (Vue3)1. 創建 `Test…

STM32最小CLion開發環境

文章目錄 1 必須文件2 工具鏈3 CLion 全局配置4 CLion 新項目配置ST-Link 調試 5 點亮 LED6 分析 elf 文件7 項目模板 1 必須文件 ST 提供的頭文件支持 MDK-ARM, GCC, IAR 3種編譯器, 下面采用 GCC 編譯器 Arm GNU Toolchain Downloads – Arm Developer 或 安裝包版 調試器服…

核函數:解鎖支持向量機的強大能力

在機器學習的世界中&#xff0c;支持向量機&#xff08;SVM&#xff09;是一種強大的分類算法&#xff0c;而核函數則是其背后的“魔法”&#xff0c;讓 SVM 能夠處理復雜的非線性問題。今天&#xff0c;我們就來深入探討核函數的奧秘&#xff0c;看看它們是如何幫助 SVM 在高維…

【Go-6】數據結構與集合

6. 數據結構與集合 數據結構是編程中用于組織和存儲數據的方式&#xff0c;直接影響程序的效率和性能。Go語言提供了多種內置的數據結構&#xff0c;如數組、切片、Map和結構體&#xff0c;支持不同類型的數據管理和操作。本章將詳細介紹Go語言中的主要數據結構與集合&#xf…

3. 簡述node.js特性與底層原理

&#x1f63a;&#x1f63a;&#x1f63a; 一、Node.js 底層原理&#xff08;簡化版&#xff09; Node.js 是一個 基于 Chrome V8 引擎構建的 JavaScript 運行時&#xff0c;底層核心由幾部分組成&#xff1a; 組成部分簡要說明 1.V8 引擎 將 JS 編譯成機器碼執行&#xff0…

Web開發主流前后端框架總結

&#x1f5a5; 一、前端主流框架 前端框架的核心是提升用戶界面開發效率&#xff0c;實現高交互性應用。當前三大主流框架各有側重&#xff1a; React (Meta/Facebook) 核心特點&#xff1a;采用組件化架構與虛擬DOM技術&#xff08;減少真實DOM操作&#xff0c;優化渲染性能&…

大語言模型備案與深度合成算法備案的區別與聯系

“什么情況下做算法備案&#xff1f;” “什么情況下做大模型備案呢&#xff1f;” 進行大模型備案的企業必然要進行算法備案&#xff0c;而進行算法備案的企業則需根據其提供的服務性質判斷是否需要進行大模型備案。 算法備案與大模型備案已經是個老生常談的話題了&#xf…

微軟PowerBI考試 PL300-Power BI 入門

Power BI 入門 上篇更新了微軟PowerBI考試 PL-300學習指南&#xff0c;今天分享PowerBI入門學習內容。 簡介 Microsoft Power BI 是一個完整的報表解決方案&#xff0c;通過開發工具和聯機平臺提供數據準備、數據可視化、分發和管理。 Power BI 可以從使用單個數據源的簡單…

【Hive入門】

之前實習寫的筆記&#xff0c;上傳留個備份。 1. 使用docker-compose快速搭建Hive集群 使用docker快速配置Hive環境 拉取鏡像 2. Hive數據類型 隱式轉換&#xff1a;窄的可以向寬的轉換顯式轉換&#xff1a;cast 3. Hive讀寫文件 SerDe:序列化&#xff08;對象轉為字節碼…

設計模式——簡單工廠模式(創建型)

摘要 本文主要介紹了簡單工廠模式&#xff0c;包括其定義、結構、實現方式、適用場景、實戰示例以及思考。簡單工廠模式是一種創建型設計模式&#xff0c;通過工廠類根據參數決定創建哪一種產品類的實例&#xff0c;封裝了對象創建的細節&#xff0c;使客戶端無需關心具體類的…

抽象工廠模式與策略模式結合使用小案例

目錄 1.前言1.示例說明1.1定義通用接口1.2 定義抽象工廠1.3 支付寶實現1.4 微信實現1.5 客戶端使用代碼&#xff08;組合使用&#xff09;1.6 示例結果輸出1.7 總結 1.前言 上一篇章就通過簡單的案例來了解抽象工廠模式和策略模式的使用&#xff0c;現在就用個支付場景的小案例…

通過WiFi無線連接小米手機攝像頭到電腦的方法

通過WiFi無線連接小米手機攝像頭到電腦的方法 以下是基于Scrcpy和DroidCam兩種工具的無線連接方案&#xff0c;需提前完成開發者模式與USB調試的開啟&#xff08;參考原教程步驟&#xff09;&#xff1a; 方法一&#xff1a;Scrcpy無線投屏&#xff08;無需手機端安裝&#xf…

2025軟件供應鏈安全最佳實踐︱證券DevSecOps下供應鏈與開源治理實踐

項目背景&#xff1a;近年來&#xff0c;云計算、AI人工智能、大數據等信息技術的不斷發展、各行各業的信息電子化的步伐不斷加快、信息化的水平不斷提高&#xff0c;網絡安全的風險不斷累積&#xff0c;金融證券行業面臨著越來越多的威脅挑戰。特別是近年以來&#xff0c;開源…