JVM 雙親委派機制

???一、從 JDK 到 JVM:Java 運行環境的基石??

????????在 Java 開發領域,JDK(Java Development Kit)是開發者的核心工具包。它不僅包含了編譯 Java 代碼的工具(如 javac),還內置了 JRE(Java Runtime Environment)—— 即 Java 程序的運行時環境。而 JVM(Java Virtual Machine)則是 JRE 的核心,它如同一個 “翻譯官”,將 Java 字節碼轉換為不同操作系統能理解的機器指令,實現了 “一次編寫,到處運行” 的跨平臺特性。

1.JVM 作用

Java 虛擬機負責裝載字節碼到其內部,解釋/編譯為對應平臺上的機器碼指令執行。

現在的 JVM 不僅可以執行 java 字節碼文件,還可以執行其他語言編譯后的字節碼文件,是一個跨語言平臺.?


?


程序在執行之前先要把 java 代碼轉換成字節碼(class 文件),jvm首先需要把字節碼通過一定的方式 類加載器(ClassLoader)把文件加載到內存中的運行時數據區(Runtime Data Area) ,而字節碼文件是jvm的一套指令集規范,并不能直接交個底層操作系統去執行,因此需要特定的命令解析器 執行引擎(Execution Engine) 將字節碼翻譯成底層系統指令再交由CPU 去執行,而這個過程中需要調用其他語言的接口本地庫接口(NativeInterface) 來實現整個程序的功能,這就是這 4 個主要組成部分的職責與功能。?

? ? ? ? 比如,當我們運行java HelloWorld時,JVM 首先通過類加載器找到HelloWorld.class文件,將二進制數據讀入內存,創建HelloWorld.class對象。


二、JVM 模塊劃分與核心功能

JVM 的架構可分為四大模塊:類加載器子系統運行時數據區執行引擎本地方法接口

1. 類加載器子系統

類加載器負責將字節碼文件加載到 JVM 中。根據職責不同,JVM 提供了三種類加載器:

  • 啟動類加載器(Bootstrap ClassLoader):用 C/C++ 實現,加載 Java 核心類庫(如rt.jar),位于%JAVA_HOME%/lib目錄。
  • 擴展類加載器(Extension ClassLoader):加載%JAVA_HOME%/jre/lib/ext目錄或java.ext.dirs指定路徑的類庫。
  • 應用程序類加載器(Application ClassLoader):加載用戶類路徑(classpath)下的類,是程序默認的類加載器。

????????類加載器采用雙親委派機制:當一個類加載器收到加載請求時,會先委托父類加載器處理,只有父類無法加載時才嘗試自己加載。這一機制確保了核心類的安全性和唯一性。例如,當嘗試加載java.lang.String時,啟動類加載器會優先加載核心庫中的 String 類,避免用戶自定義類覆蓋核心類。

2. 運行時數據區

運行時數據區是 JVM 在執行程序時分配的內存區域,包含以下部分:

  • 程序計數器:記錄當前線程執行的字節碼指令地址,是線程私有的最小內存空間。
  • Java 虛擬機棧:每個線程創建時生成,保存方法調用的棧幀(包含局部變量表、操作數棧、方法返回地址等),線程私有,可能出現棧溢出(StackOverflowError)。
  • 本地方法棧:管理本地方法(如 C/C++ 實現的方法)的調用。
  • 堆內存:存儲對象實例,是 GC(垃圾回收)的主要區域,分為新生代(Eden 和 Survivor 區)和老年代,通過分代收集算法優化回收效率。
  • 方法區:存儲類的元數據(如字節碼、靜態變量、常量池),JDK8 后稱為元空間(Metaspace),邏輯上獨立于堆。

3. 執行引擎

執行引擎是 JVM 的 “大腦”,負責將字節碼轉換為機器指令。它包含:

  • 解釋器:逐行解釋執行字節碼,啟動快但效率較低。
  • JIT 編譯器:將頻繁執行的 “熱點代碼” 編譯為本地機器碼,存儲在方法區的 JIT 緩存中,提升執行效率。
  • 垃圾回收器:自動回收不再使用的對象,主要針對堆內存,采用標記 - 復制、標記 - 清除、標記 - 壓縮等算法。

4. 本地方法接口

????????本地方法接口允許 Java 調用非 Java 代碼(如 C/C++),通過本地方法庫實現與操作系統或硬件的交互,例如文件操作、網絡通信等。

三、類加載的核心機制與實踐

1. 類加載的作用與過程

類加載的核心任務是將字節碼文件轉換為 JVM 可識別的 Class 對象。這一過程分為五個階段:

  1. 加載(Loading):通過類的全限定名獲取二進制字節流,生成 Class 對象。
  2. 驗證(Verification):檢查字節碼的安全性和合規性,防止惡意代碼攻擊。
  3. 準備(Preparation):為靜態變量分配內存并設置默認初始值(如 int 初始化為 0)。
  4. 解析(Resolution):將符號引用轉換為直接引用(如將類名轉換為內存地址)。
  5. 初始化(Initialization):執行靜態代碼塊和靜態變量賦值,這是類加載的最后一步。

2. 類加載的觸發時機

????????類加載遵循 “按需加載” 原則,只有在需要使用類時才會觸發。根據 JVM 規范,以下情況會強制加載類(主動引用):

假設我們有一個 Hello 類

package com.ffyc.classload;/*** 問題:什么時候類會被加載?**/
public class Hello {// 作為靜態成員時,類會被加載static {System.out.println("類被加載了......");}//作為main方法時,類也會被加載public static void main(String[] args) {System.out.println("1111111");}}

TestHello 類?

package com.ffyc.classload;public class TestHello {public static void main(String[] args) throws ClassNotFoundException {new Hello(); // 觸發Hello類的初始化,并加載Hello類Class.forName("com.ffyc.classload.Hello");//反射方式加載Hello類}}

3. 類加載的典型案例

(1) 主動引用(必定觸發加載)
  • new?實例化對象
    MyClass obj = new MyClass();  // 首次創建對象時加載
    
  • 訪問類的靜態變量或靜態方法
    int value = MyClass.staticField;  // 訪問靜態字段
    MyClass.staticMethod();           // 調用靜態方法
    
  • 反射調用(Class.forName()
    Class.forName("com.example.MyClass");  // 通過反射強制加載
    
  • 初始化子類時(父類優先加載)
    class Parent {}
    class Child extends Parent {}  
    // 首次使用 Child 時,會先加載 Parent
    
  • 作為程序入口的主類(main?方法所在類)
    public class Main {  public static void main(String[] args) {}  // JVM 啟動時加載
    }
    
(2) 被動引用(不會觸發加載)
  • 通過子類引用父類的靜態字段
    class Parent { static int value = 10; }
    class Child extends Parent {}
    System.out.println(Child.value);  // 僅加載 Parent,不加載 Child
    
  • 通過數組定義類
    MyClass[] arr = new MyClass[10];  // 不會加載 MyClass
    
  • 引用常量(常量在編譯期優化)
    class MyClass { final static int VALUE = 10; }
    System.out.println(MyClass.VALUE);  // 不觸發加載(常量池直接訪問)

四、類加載器分類

站在 java 開發人員的角度來看,類加載器就應當劃分得更細致一些. 保持者三層類加載器.

1. 啟動類加載器 (BootStrap ClassLoader)

????????這個類加載器使用 C/C++語言實現,也叫引導類加載器,嵌套在 JVM 內部.它用來加載java 核心類庫.負責加載擴展類加載器和應用類加載器,并為他們指定父類加載器. 出于安全考慮,啟動類加載器只加載存放在\lib 目錄,或者被-Xbootclasspath 參數鎖指定的路徑中存儲放的類.

2.?擴展類加載器(Extension ClassLoader)

????????Java 語言編寫的,由 sun.misc.Launcher$ExtClassLoader 實現. 派生于 ClassLoader 類. 從 java.ext.dirs 系統屬性所指定的目錄中加載類庫,或從JDK 系統安裝目錄的jre/lib/ext 子目錄(擴展目錄)下加載類庫.如果用戶創建的jar 放在此目錄下,也會自動由擴展類加載器加載.

3. 應用程序類加載器(系統類加載器 Application ClassLoader)

????????Java 語言編寫的,由 sun.misc.Launcher$AppClassLoader 實現.派生于 ClassLoader 類. 加載我們自己定義的類,用于加載用戶類路徑(classpath)上所有的類. 該類加載器是程序中默認的類加載器.ClassLoader 類 , 它 是 一 個 抽 象 類 , 其 后 所 有的類加載器都繼承自ClassLoader(不包括啟動類加載器)

?五、雙親委派機制

????????Java 虛擬機對 class 文件采用的是按需加載的方式,也就是說當需要該類時才會將它的 class 文件加載到內存中生成 class 對象.而且加載某個類的class 文件時,Java 虛擬機采用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式.

工作原理:

1. 如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行.

2. 如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器.

3. 如果父類加載器可以完成類的加載任務,就成功返回,倘若父類加載器無法完成加載任務,子加載器才會嘗試自己去加載,這就是雙親委派機制. 如果均加載失敗,就會拋出 ClassNotFoundException 異常。

那么思考一下,如果我們自定義一個 String 類,會被加載嗎?

直接上示例去驗證你的答案

?

package java.lang;/*** 測試雙親委派機制*/
public class String {static {System.out.println("自定義 String 類被加載!");}
}
package com.ffyc.classload;public class TestHello {public static void main(String[] args) {new Hello(); // 觸發Hello類的初始化,并加載Hello類try {Class.forName("com.ffyc.classload.Hello");//反射方式加載Hello類Class.forName("java.lang.String");} catch (ClassNotFoundException e) {throw new RuntimeException(e);}System.out.println("String類加載器為:"+ String.class.getClassLoader()+"所以屬于啟動類加載器");System.out.println("Hello類加載器為:"+Hello.class.getClassLoader()+"所以屬于系統類加載器");}}

輸出結果為,并沒有看到??"自定義 String 類被加載!"? 這句話

????????可以看到,自定義的String 類雖然和jdk的String類同包同名,但還是沒有被加載,這就是雙親委派機制,那么問題來了,我就想讓它加載我自己定義的String類,該怎么做?

六、如何打破雙親委派機制

????????Java 虛擬機的類加載器本身可以滿足加載的要求,但是也允許開發者自定義類加載器。 在 ClassLoader 類中涉及類加載的方法有兩個,loadClass(String name), findClass(String name),這兩個方法并沒有被 final 修飾,也就表示其他子類可以重寫. 重寫 findClass 方法 我們可以通過自定義類加載重寫方法打破雙親委派機制, 再例如 tomcat 等都有自己定義的類加載器.

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class CustomClassLoader extends ClassLoader {private final String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 檢查是否已加載過該類Class<?> loadedClass = findLoadedClass(name);if (loadedClass != null) {return loadedClass;}// 打破雙親委派:先嘗試自己加載,再委托父類try {// 自定義加載邏輯(例如從指定路徑加載類)byte[] classBytes = loadClassBytes(name);if (classBytes != null) {return defineClass(name, classBytes, 0, classBytes.length);}} catch (IOException e) {// 加載失敗,繼續委托父類加載器}// 委托父類加載器(保留原有機制的兜底)return super.loadClass(name, resolve);}}private byte[] loadClassBytes(String className) throws IOException {// 將類名轉換為文件路徑(例如com.example.MyClass → /path/com/example/MyClass.class)String path = classPath + File.separator + className.replace('.', File.separatorChar) + ".class";File file = new File(path);if (!file.exists()) {return null;}try (FileInputStream fis = new FileInputStream(file)) {byte[] bytes = new byte[(int) file.length()];fis.read(bytes);return bytes;}}
}
public class Main {public static void main(String[] args) throws Exception {// 創建自定義類加載器,指定加載路徑CustomClassLoader loader = new CustomClassLoader("/path/to/classes");// 加載自定義類(優先從指定路徑加載)Class<?> clazz = loader.loadClass("com.example.MyClass");Object instance = clazz.newInstance();System.out.println(instance.getClass().getClassLoader()); // 輸出:CustomClassLoader}
}

七、總結

????????JVM 的類加載機制是 Java 程序運行的基石,它通過類加載器、運行時數據區和執行引擎的協同工作,確保了程序的跨平臺性和高效執行。理解類加載的過程、時機和類加載器的工作原理,不僅能幫助開發者優化程序性能,還能深入排查類沖突、內存泄漏等問題。無論是日常開發還是高級調優,掌握 JVM 類加載機制都是成為優秀 Java 工程師的必經之路。

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

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

    相關文章

    java開發之異常

    一 結構 Throwable分為Exception和error Exception分為RuntimeException&#xff08;運行時異常&#xff09;和其他異常 主動拋出運行時異常和非運行時異常的區別 1、throw RuntimeException&#xff08;或運行時異常的子類&#xff09; 編譯時不會報錯。 2、throw Excepti…

    MySQL 中 JOIN 和子查詢的區別與使用場景

    目錄 一、JOIN:表連接1.1 INNER JOIN:內連接1.2 LEFT JOIN:左連接1.3 RIGHT JOIN:右連接1.4 FULL JOIN:全連接二、子查詢:嵌套查詢2.1 WHERE 子句中的子查詢2.2 FROM 子句中的子查詢2.3 SELECT 子句中的子查詢三、JOIN 和子查詢的區別3.1 功能差異3.2 性能差異3.3 使用場…

    2025年第三屆盤古石杯初賽(智能冰箱,監控部分)

    前言 所以去哪里可以取到自己家里的智能家居數據呢&#xff1f;&#xff1f;&#xff1f;&#xff1f; IOT物聯網取證 1、分析冰箱&#xff0c;請問智能冰箱的品牌&#xff1f; [答案格式&#xff1a;xiaomi] Panasonic2、請問智能冰箱的型號&#xff1f; [答案格式&#x…

    【強化學習】強化學習算法 - 馬爾可夫決策過程

    文章目錄 馬爾可夫決策過程 (Markov Decision Process, MDP)1. MDP 原理介紹2. MDP 建模/實現步驟3. MDP 示例&#xff1a;簡單網格世界 (Grid World) 馬爾可夫決策過程 (Markov Decision Process, MDP) 1. MDP 原理介紹 馬爾可夫決策過程 (MDP) 是強化學習 (Reinforcement L…

    用戶現場不支持路由映射,如何快速將安防監控EasyCVR視頻匯聚平臺映射到公網?

    一、方案背景? 隨著數字化安防與智能交通管理發展&#xff0c;視頻監控遠程管理需求激增。EasyCVR作為專業視頻融合平臺&#xff0c;具備多協議接入等核心功能&#xff0c;是智能監控的重要工具。但實際部署中&#xff0c;當EasyCVR處于內網且路由器無法進行端口映射時&#…

    MODBUS RTU調試助手使用方法詳解

    一、軟件簡介 485調試助手是一款常用的串口通信調試工具&#xff0c;專門用于RS-485總線設備的測試、調試和通信監控。它支持多種串口參數設置&#xff0c;提供數據收發功能&#xff0c;是工業現場調試的必備工具之一。 二、軟件安裝與啟動 1. 系統要求 Windows 7/10/11操作…

    ECMAScript 2018(ES2018):異步編程與正則表達式的深度進化

    1.版本背景與發布 發布時間&#xff1a;2018年6月&#xff0c;由ECMA International正式發布&#xff0c;標準編號為ECMA-262 9th Edition。歷史意義&#xff1a;作為ES6之后的第三次年度更新&#xff0c;ES2018聚焦于異步編程、正則表達式和對象操作的標準化&#xff0c;推動…

    【C語言】鏈接與編譯(編譯環境 )

    前言&#xff1a; 在前面講解文件操作&#xff0c;了解了文件的類別&#xff0c;文件的打開與關閉&#xff0c;字符讀寫函數&#xff0c; 字符串讀寫函數&#xff0c;格式化輸入輸出函數 在C語言編程中&#xff0c;編譯與鏈接是將源代碼轉化為可執行程序的關鍵步驟。為了詳細…

    Java視頻流RTMP/RTSP協議解析與實戰代碼

    在Java中實現視頻直播的輸入流處理&#xff0c;通常需要結合網絡編程、多媒體處理庫以及流媒體協議&#xff08;如RTMP、HLS、RTSP等&#xff09;。以下是實現視頻直播輸入流的關鍵步驟和技術要點&#xff1a; 1. 視頻直播輸入流的核心組件 網絡輸入流&#xff1a;通過Socket或…

    系分論文《論系統需求分析方法及應用》

    系統分析師論文范文系列 【摘要】 2022年6月&#xff0c;我作為系統分析師參與了某金融機構“智能信貸風控系統”的建設項目。該系統旨在通過對業務流程的數字化重構&#xff0c;優化信貸審批效率并降低風險。項目涉及信貸申請、資質審核、風險評估、額度審批等核心流程&#x…

    stack和queue簡單模擬實現

    stackreverse_iteratorqueuepriority_queue仿函數具體代碼 stack Stacks are a type of container adaptor, specifically designed to operate in a LIFO context (last-in first-out), where elements are inserted and extracted only from one end of the container. 上述描…

    Linux內核可配置的參數

    sysctl -a 命令會列出當前Linux內核所有可配置的參數及其當前值。這些參數允許你在系統運行時動態地調整內核的行為&#xff0c;而無需重新編譯內核或重啟系統。 內容非常多&#xff0c;因為內核有很多可調的方面。我們可以把它們大致分為幾個主要類別&#xff1a; kernel.*: …

    【背包dp-----分組背包】------(標準的分組背包【可以不裝滿的 最大價值】)

    通天之分組背包 題目鏈接 題目描述 自 01 01 01 背包問世之后&#xff0c;小 A 對此深感興趣。一天&#xff0c;小 A 去遠游&#xff0c;卻發現他的背包不同于 01 01 01 背包&#xff0c;他的物品大致可分為 k k k 組&#xff0c;每組中的物品相互沖突&#xff0c;現在&a…

    操作系統:os概述

    操作系統&#xff1a;OS概述 程序、進程與線程無極二級目錄三級目錄 程序、進程與線程 指令執行需要那些條件&#xff1f;CPU內存 需要數據和 無極 二級目錄 三級目錄

    RAG文本分塊

    不論是向量化模型還是大語言模型&#xff0c;都存在輸入長度的限制。對于超過限制的文本&#xff0c;模型會進行截斷&#xff0c;造成語義缺失。分塊可以確保每個文本片段都在模型的處理范圍內&#xff0c;避免重要信息的丟失。 文本分塊的核心原則 高質量分塊的核心原則是&a…

    2025 年九江市第二十三屆中職學校技能大賽 (網絡安全)賽項競賽樣題

    2025 年九江市第二十三屆中職學校技能大賽 &#xff08;網絡安全&#xff09;賽項競賽樣題 &#xff08;二&#xff09;A 模塊基礎設施設置/安全加固&#xff08;200 分&#xff09;A-1 任務一登錄安全加固&#xff08;Windows,Linux&#xff09;A-2 任務二 Nginx 安全策略&…

    量子隧穿:PROFINET到Ethernet ip的無損耗協議轉換方案轉

    在本季度的生產工作中&#xff0c;我們成功實現了倉儲物流自動化分揀系統中的關鍵技術突破。我們面臨的主要挑戰是將采用EtherNet/IP協議的輸送帶控制器與PROFINET協議的上位系統進行有效通信。通過引入ethernet IP轉PROFINET網關倍訊科技BX-606-EIP&#xff0c;我們實現了輸送…

    OpenCV CUDA模塊中矩陣操作------降維操作

    操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::cuda::reduce 函數用于對 GPU 上的矩陣沿某個維度進行降維操作&#xff0c;例如求和、取最大值等。此函數支持多種降維操作&#xff0c;并允…

    一分鐘用 MCP 上線一個 貪吃蛇 小游戲(CodeBuddy版)

    我正在參加CodeBuddy「首席試玩官」內容創作大賽&#xff0c;本文所使用的 CodeBuddy 免費下載鏈接&#xff1a;騰訊云代碼助手 CodeBuddy - AI 時代的智能編程伙伴 你好&#xff0c;我是悟空。 背景 上篇我們用 MCP 上線了一個 2048 小游戲&#xff0c;這次我們繼續做一個 …

    簡單神經網絡(ANN)實現:從零開始構建第一個模型

    本文將手把手帶你用 Python Numpy 實現一個最基礎的人工神經網絡&#xff08;Artificial Neural Network, ANN&#xff09;。不依賴任何深度學習框架&#xff0c;適合入門理解神經網絡的本質。 一、項目目標 構建一個三層神經網絡&#xff08;輸入層、隱藏層、輸出層&#xf…