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.mzph.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…