【Java虚拟机】JVM类加载机制和双亲委派模型

news2025/6/19 13:15:59

文章目录

        • 1.JVM虚拟机类加载子系统
        • 2.双亲委派机制和JDK9模块化系统
        • 3.ClassLoader源码解读和自定义类加载器场景
        • 4.自定义ClassLoader类加载器案例实战
        • 5.不同类加载器加载同个class类

1.JVM虚拟机类加载子系统

(1)什么是类加载子系统

  • 是Java虚拟机的一个重要子系统,主要负责将类的字节码加载到JVM内存的方法区,并将其转换为JVM内部的数据结构。

在这里插入图片描述

(2)类加载器的三大特点

  • 双亲委派模型
    • Java虚拟机采用双亲委派模型来加载类,即先从父类加载器中查找类,如果找到了就直接返回
    • 否则再由自己的加载器加载,这种模型可以避免类的重复加载,提高系统的安全性。
  • 延迟加载
    • Java虚拟机采用延迟加载的策略,即只有在需要使用某个类时才进行加载
    • 这种策略可以减少系统启动时间,提高系统的性能
  • 动态加载
    • Java虚拟机支持动态加载类,即可以在程序运行时动态地加载和卸载类
    • 这种特性可以使Java程序更加灵活和可扩展

(3)类加载子系统由三个模块组成

  • 加载器(ClassLoader)

    • 加载器负责将类的字节码加载到JVM中

    • 三种类型的加载器

      • 启动类加载器(Bootstrap ClassLoader)

        • c/c++实现,加载核心类库使用,不继承ClassLoader,没父加载器
      • 平台类加载器(Platform ClassLoader)

        • 注意:JDK9之前是扩展类加载器 Extension ClassLoader
      • 应用程序类加载器(Application ClassLoader)

        • 程序的默认加载器,我们写的代码基本都是由这个加载器负责加载
      • 类加载器用父类加载器、子类加载器这样的名字,虽然看似是继承关系,实际上是组合(Composition)关系

  • 链接器(Linker)

    • 负责将Java类的二进制代码链接到Java虚拟机中,并生成可执行的Java虚拟机代码,包括 验证、准备和解析
    • 验证操作主要是验证类的字节码是否符合JVM规范
    • 准备操作主要是为类的静态变量分配内存并设置默认值
    • 解析操作主要是将符号引用转换为直接引用
  • 初始化器(Initializer)

    • 负责执行Java类的静态初始化,包括静态变量的赋值、静态代码块的执行等
    • 初始化器是类加载子系统的最后一个阶段

2.双亲委派机制和JDK9模块化系统

(1)为啥需要这个双亲委派模型

  • 比如java.lang.Object 这些存放在rt.jar中的类,无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载
  • 不同加载器加载的Object类都是同一个,如果没有使用双亲委派模型,各个类加载器自行去加载的话,就会出现问题
  • 比如用户编写了一个称为java.lang.Object或String的类,并放在classpath下
  • 那系统将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证

(2)什么是双亲委派模型

  • 是一种类加载机制,类加载器之间形成了一条类加载器链,每个类加载器都有一个父类加载器,形成了从下到上的一条继承链
  • 如果所有的类加载器都无法加载该类,则会抛出 ClassNotFoundException 异常
  • 加载流程
    • 一个类的加载请求首先会被委派给其父类加载器进行处理
    • 如果父类加载器无法加载该类,则会将加载请求委派给其自身进行加载
    • 如果自身也无法加载该类,则会将加载请求委派给其子类加载器进行处理,直到找到能够加载该类的类加载器为止
  • 优点
    • 可以保证类的唯一性和安全性。由于每个类加载器都只能加载自己的命名空间中的类
    • 由于类加载器之间形成了一条继承链,因此可以保证类的安全性,防止恶意代码的注入

在这里插入图片描述

(3)什么是JDK9模块化系统

  • 是一种新的Java平台的组织方式,将Java SE分成多个模块,每个模块都有自己的API和实现
  • 每个模块都有一个唯一的标识符和版本号,可以独立地进行开发、测试、部署和维护
  • 模块之间的依赖关系通过模块描述文件(module-info.java)来声明
  • 这个文件包含模块的名称、版本号、导出的包、依赖的模块等信息
  • 在编译和运行时,模块系统会根据模块描述文件来加载和链接模块,确保模块之间的依赖关系正确

(4)新版本的JDK9后的类加载器

  • 模块化系统中的类加载器可以分为两种类型

    • 应用程序类加载器,加载器用于加载应用程序中的模块; 平台类加载器,加载器用于加载 JDK 中的模块

    • 当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中

    • 如果findLoadedModule 可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载

  • 在模块化系统中,每个模块都有一个类加载器,它根据模块的依赖关系来加载模块中的类和依赖的模块中的类

  • 在模块化系统中,类加载器的原理与传统的类加载器相似,都是采用双亲委派模型

  • 当一个类被加载时,类加载器首先会检查自己是否已经加载过该类,如果没有,则会将该类的加载请求委托给其父加载器

  • 直到达到顶层的 Bootstrap ClassLoader 为止,如果所有的父加载器都无法加载该类,则由当前加载器自己来加载该类

(5)新版本的JDK9后的类加载器

  • 模块化系统中的类加载器可以分为两种类型

    • 应用程序类加载器,加载器用于加载应用程序中的模块; 平台类加载器,加载器用于加载 JDK 中的模块

    • 当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中

    • 如果findLoadedModule 可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载

  • 在模块化系统中,每个模块都有一个类加载器,它根据模块的依赖关系来加载模块中的类和依赖的模块中的类

  • 在模块化系统中,类加载器的原理与传统的类加载器相似,都是采用双亲委派模型

  • 当一个类被加载时,类加载器首先会检查自己是否已经加载过该类,如果没有,则会将该类的加载请求委托给其父加载器

  • 直到达到顶层的 Bootstrap ClassLoader 为止,如果所有的父加载器都无法加载该类,则由当前加载器自己来加载该类

在这里插入图片描述

  • 关键类BuiltinClassLoader
    • BuiltinClassLoader 是 jdk9 中代替 URLClassLoader 的加载器,是 PlatformClassLoader 与 AppClassLoader 的父类

3.ClassLoader源码解读和自定义类加载器场景

(1)为什么需要用到自定义类加载器

  • 为 Java 应用程序提供更加灵活和可定制的类加载机制
  • 场景案例
    • 拓展加载源
      • 从网络,数据库等地方加载类
    • 防止源码泄漏
      • 自定义类加载器可以加载加密的类文件,保护类的安全性
    • 实现类隔离(tomcat里面大量应用)
      • 自定义类加载器可以实现类隔离,避免类之间的冲突和干扰
      • 注意
        • 比较两个类是否相等,只有两个类是由同一个类加载器加载的前提下才有意义
        • 否则即使两个类来自同一个class文件,但是由于加载他们的类加载器不同,那这两个类就不相等
        • 不同类加载器加载同一个class文件得到的类型是不同的

(2)ClassLoader核心源码解读

  • loadClass
    • 用于加载指定名称的类,双亲委派模型核心实现,一般不建议重写相关方法,直接由ClassLoader自己实现
    • 遵循双亲委派模型,首先委派给父类加载器进行加载,如果父类加载器无法加载该类,则自身进行加载
  • findClass
    • 是用于查找类的方法,它通常由子类加载器实现,用于查找自身命名空间中的类
    • 由于历史JDK1.2之前版本兼容问题,自定义类加载器则推荐重写这个方法
    • findClass()方法是在loadClass()方法中调用,当loadClass()方法中加载失败后,则调用自己的findClass()方法来完成类加载
    • 但是ClassLoader的findClass没有实现,需要自己实现具体逻辑,findClass方法通常是和defineClass方法一起使用的
  • defindClass
    • 是用于定义类的方法,它将字节数组转换为 Class 对象,并将其添加到类加载器的命名空间中
    • ClassLoader方法里面已经实现,findClass方法通常是和defineClass方法一起使用的
  • resolveClass
    • 是用于解析类的方法,它将类的引用转换为实际的类,并进行链接和初始化
		protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            //首先检查这个class是否已经加载过了

            Class<?> c = findLoadedClass(name);
            //c==null表示没有加载
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果有父类的加载器则让父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //如果父类的加载器为空 则递归到bootStrapClassloader,这里就是双亲委派模型的实现
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                  /如果bootstrapClassLoader 仍然没有加载过,则自己去加载class
                    long t1 = System.nanoTime();
                  //是一个空方法,返回内容为class,方法其中没有任何内容,只抛出了个异常,说明这个方法需要开发者自己去实现
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • 注意两个方法区别

    • findClass( ) 用于写类加载逻辑

    • loadClass( )方法的逻辑里,如果父类加载器加载失败则会调用自己的findClass( )方法完成加载,保证了双亲委派规则

      • 如果不想打破双亲委派模型,那么只需要重写findClass方法即可
      • 如果想打破双亲委派模型,多数情况下需要重写整个loadClass方法

4.自定义ClassLoader类加载器案例实战

  • 自定义类加载器流程
    • 继承ClassLoader类
    • 重写loadClass方法(会破坏双亲委派机制,不推荐)
    • 重写findClass方法 (推荐)
  • 先定义User类,将User.java定义成User.class
@Data
public class User {

    private String name;

    private int age;

}
javac User.java

在这里插入图片描述

  • 编码实战
    • 自定义类加载器
public class MyClassLoader extends ClassLoader{

    private String codePath;

    public MyClassLoader(String codePath){
        this.codePath = codePath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = codePath + name + ".class";
        System.out.println(fileName);
        //获取输入流
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
             ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                bos.write(data, 0, len);
            }
            //获取内存中字节数组
            byte[] byteCode = bos.toByteArray();
            //执行 defineClass 将字节数组转成Class对象
            Class<?> defineClass = defineClass(null, byteCode, 0, byteCode.length);
            return defineClass;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        //测试自定义类加载器
        MyClassLoader myClassLoader = new MyClassLoader("/Users/mac/IdeaProjects/jvm-test/src/main/java/com/lixiang/");
        //加载指定类
        Class<?> clazz = myClassLoader.loadClass("User");
        //创建对像
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println( "当前类:"+obj.getClass().getName());
        System.out.println( "当前类加载器:"+obj.getClass().getClassLoader().getClass().getName());
    }

}

在这里插入图片描述

使用自定义的类加载器进行加载 。

5.不同类加载器加载同个class类

问题:两个不同的类加载器加载同一个class类,JVM是否认为它们相同

编码验证

public static void main(String[] args) throws Exception {
        //测试不同classloader加载同个class
        MyClassLoader myClassLoader = new MyClassLoader("/Users/mac/IdeaProjects/jvm-test/src/main/java/com/lixiang/");
        //用自定义的类加载器加载User.class
        Class<?> clazz = myClassLoader.loadClass("User");
        //创建对象
        Object obj = clazz.getDeclaredConstructor().newInstance();
        //判断创建的对象是否是User对象
        System.out.println("判断创建的对象是否是User对象:"+(obj instanceof User));
    }

在这里插入图片描述

				//打印相应的类加载器
        System.out.println("obj 的 类加载器:"+obj.getClass().getClassLoader().getClass().getName());
        System.out.println("User 的 类加载器:"+User.class.getClassLoader().getClass().getName());

在这里插入图片描述

  • 在 JVM 中,不同类加载器加载同一个类时,可能会出现重复加载的情况
  • 当不同类加载器加载同一个类时,每个类加载器都会在自己的命名空间中创建一个新的 Class 对象
  • 即使这些 Class 对象的字节码是一样的,也会被认为是不同的类
  • 重复加载同一个类会导致一些问题,例如类的静态变量和代码块会被多次执行,导致出现意料之外的行为
  • JVM 采用了类的双亲委派模型来避免重复加载同一个类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MinIO基础教程

MinIO 1.MinIO安装 Minio 是个基于 Golang 编写的开源对象存储服务&#xff0c;存储非结构化数据&#xff0c;如&#xff1a;图片&#xff0c;视频&#xff0c;音乐等 官网地址&#xff1a;https://min.io/ 中文地址&#xff1a;http://minio.org.cn 官网文档&#xff08; …

【LeetCode: 300. 最长递增子序列 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Vue】收集表单数据 过滤器

收集表单数据 收集表单数据&#xff1a; 若&#xff1a;<input type"text"/>,则v-model收集的是value值&#xff0c;用户输入的就是value值若&#xff1a;<input type"radio"/>,则v-model收集的是value值&#xff0c;且要给标签配置value值若…

树莓派与STM32(rt1064)串口通信

目录 一、树莓派通信 1、硬件连线准备 2、安装Serial和打开树莓派串口 2.1安装Serial 2.2打开树莓派串口 2.3修改串口映射关系 3、树莓派代码 4、上位机 5、运行uart.py代码进行测试 5.1 树莓派发送&#xff0c;上位机接收 5.2上位机发送&#xff0c;树莓派接收 二、…

HopeHomi脚手架(四)redis、redisson模块

项目结构 Redis RedisSpiModuleImport 基于SPI。在项目启动的时候返回待加载类名 public class RedisSpiModuleImport implements SpiEnvironmentModuleImport {Overridepublic String[] readyImportClassName() {return new String[]{RedisConfiguration.class.getName()};…

裸机配置Java环境,解决 -bash: jps: command not found

目录 配置JDK 1、第一步&#xff1a;使用yum命令查找JDK 2、第二步&#xff1a;执行安装命令 3、第三步&#xff1a;验证是否安装成功 4、第四步&#xff1a;验证是否可用 5、第五步&#xff1a;安装开发环境 6、第六步&#xff1a;配置环境变量 今天申请了公司的开发机器&…

【openGauss实战10】备份与恢复

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

视觉SLAM ch12 建图(RGB-D)

一、RGB-D稠密建图 RGB-D相机通结构光和飞行时间获取深度。 稠密重建方法&#xff1a;根据估计的相机位姿&#xff0c;将RGB-D数据转化为点云&#xff0c;然后进行拼接&#xff0c;最终得到由离散的点组成的点云地图。 在此基础上&#xff0c;如果希望估计物体的表面&#x…

Python 实验二 Python语言基础

1.运用输入输出函数编写程序&#xff0c;将华氏温度转换成摄氏温度。换算公式&#xff1a;C(F-32)*5/9,其中 C为摄氏温度&#xff0c;F为华氏温度。 Ffloat(input("请输入你要转换的华氏温度&#xff1a;")) C(F-32)*5/9 print("转换为摄氏温度为&#xff1a;&…

opencv:介绍 SIFT(尺度不变特征变换)及其使用(一)

在本章中 我们将了解 SIFT 算法的概念 我们将学习如何找到 SIFT 关键点和描述符。 理论 在过去的几章中,我们了解了一些角点检测器,如 Harris 等。它们具有旋转不变性,这意味着即使图像旋转,我们也可以找到相同的角点。这是显而易见的,因为旋转后的图像中的角点仍然是角点…

Cairo 1 实战入门:编写测试部署ERC-20代币智能合约

概述 读者可以前往 我的博客 获得更好的阅读体验。 Cairo 是 ZK Rollup 的领域专用语言&#xff0c;目前仅用于 StarkNet 项目。随着 Rollup 叙事的发展&#xff0c;我们认为 cairo 在未来一定会成为智能合约开发的核心语言。 本文类似我之前编写的 Foundry教程&#xff1a;…

BART模型简单介绍

目录 一、概要 二、深入扩展 2.1 预训练任务 2.2 模型精调 一、概要 BART&#xff08;Bidirectional and Auto-Regressive Transformers&#xff09;模型使用标准的基于Transformer的序列到序列结构&#xff0c;主要区别在于用GeLU&#xff08;Gaussian Error Linerar Units…

西安石油大学C++上机实验 上机二:友元和运算符重载程序设计(2学时)

上机二&#xff1a;友元和运算符重载程序设计&#xff08;2学时&#xff09; 实验目的&#xff1a; 了解友元函数的概念。 掌握友元函数的定义和使用方法。 了解运算符重载的概念和使用方法。 掌握几种常用的运算符重载的方法。 实验内容 编写一个时间类&#xff0c;实现…

【面试】记一次中小公司某一次面试题

文章目录1. MySQL中explain执行计划你比较关注哪些字段&#xff1f;2.char、varchar 和 text的区别&#xff1f;3. int(3)和int(11)查询的区别&#xff1f;4. 字段里NULL和空值的区别&#xff1f;5. spring中怎么解决循环依赖问题&#xff1f;5.1 重新设计5.2 使用注解 Lazy5.3…

ESP32设备驱动-HDC2080温湿度传感器驱动

HDC2080温湿度传感器驱动 文章目录 HDC2080温湿度传感器驱动2、硬件准备3、软件准备4、驱动实现HDC2080 器件是一款集成式湿度和温度传感器,可在小型 DFN 封装中以极低的功耗提供高精度测量。基于电容的传感器包括新的集成数字功能和用于消散冷凝和湿气的加热元件。 HDC2080 数…

虹科新闻 | 冲击记录仪ASPION G-Log 2荣获行业领袖奖-Best of Industry Award

虹科ASPION G-Log 2 荣获 行业领袖奖 “2022 Best of Industry Award” 创新的运输冲击数据记录仪虹科ASPION G-Log 2荣获2022年行业领袖奖。经过5个月的投票和近2万张选票&#xff0c;G-Log 2在传感器 由冲击或振动&#xff0c;甚至是温度或相对湿度等气候因素引起的运输损坏…

ERTEC200P-2 PROFINET设备完全开发手册(5-3)

5.3 标识和维护数据&#xff08;I&M&#xff09; 标识和维护数据是一类特殊的数据记录&#xff0c;其中 “I&M0”&#xff08;“16#AFF0”&#xff09;用于有关模块或设备的常规信息。包含的信息例如&#xff1a;订货号/Order-ID, 硬件软件版本/hard- and software v…

强化学习之蒙特卡罗(MC)、动态规划(DP)、时间差分(TD)

强化学习笔记1.马尔可夫决策过程(MDP)1.马尔可夫性质2.马尔可夫过程3.马尔可夫奖励过程(MRP)4.马尔可夫决策过程(MDP)2.蒙特卡罗&#xff08;MC&#xff09;、动态规划&#xff08;DP&#xff09;、时间差分&#xff08;TD&#xff09;1.蒙特卡罗(MC)2.动态规划(DP)3.时间差分(…

【C】Function

系列连载 【python / C / C】 参考 《C语言程序设计&#xff08;第四版&#xff09;谭浩强》【C语言】C语言视频教程《郝斌 C 语言自学教程》 文章目录为什么需要函数什么叫函数如何定义函数函数的分类注意的问题常用的系统函数为什么需要函数 避免重复性操作 有利于程序模…

23种设计模式之建造者模式(黑马程序员)

建造者模式一、概述二、结构三、实现四、总结五、模式扩展在最后一、概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构造&#xff08;由Build来负责&#xff09;和装配&#xff08;Director负责&#xff09;。从而可以…