JVM 类加载器有哪些?双亲委派机制的作用是什么?如何自定义类加载器?

news2025/1/11 22:42:39

类加载器分类

大家好,我是码哥,可以叫我靓仔,《Redis 高手心法》畅销书作者。

2477fc6cf5195ee75124cafea83daf03.jpeg

先回顾下,在 Java 中,类的初始化分为几个阶段: 加载链接(包括验证、准备和解析)和 初始化

类加载器(Class Loader)则是加载阶段中,负责将本地或网络中的指定类的二进制流,加载到 Java 虚拟机中的工具。

4647a2addca94fe2bf39c98452c19f4b.png

在进入正文前,介绍下我的《Java 面试高手心法 58 讲》专栏,帮助你在现在就业压力巨大的情况下比别人多一个杀手锏,拿下心仪 offer。内容涵盖 Java 基础、Java 高级进阶、Redis、MySQL、消息中间件、微服务架构设计等面试必考点、面试高频点。

0e4bf9e45cfaa596281aba3e4b855e2f.jpeg

丢掉你收藏的那些所谓的「面试宝典」,因为它们大多数深度不够,甚至内容还有错误,你只会看完就忘,还浪费时间。这也是为何每次面试你都回答不好,找不到好工作的原因。

3be17ba12e6104b800a20cb47b2eda70.png

引导类加载器 BootstrapClassLoader

引导类加载器 BootstrapClassLoader:引导类加载器是使用 C++ 语言实现的,嵌入在 JVM 中。用于加载 Java 中的核心类库的,不继承自 java.lang.ClassLoader,在 Java 程序中通常返回 null

一般会加载 JAVA_HOME 目录下的 /jre/lib 文件夹下的 jar 和配置。

ClassLoader loader = String.class.getClassLoader();
System.out.println(loader); // 输出 null,因为 String 是由引导类加载器加载的

扩展类加载器 ExtClassLoader

扩展类加载器主要负责加载 Java 的扩展类库,一般会加载 JAVA_HOME 目录下的 /jre/lib/ext 文件夹下的 jar。

继承自 java.lang.ClassLoader,是用户可以访问的第一个类加载器。

ClassLoader extLoader = ClassLoader.getSystemClassLoader().getParent();
System.out.println(extLoader); // 输出 sun.misc.Launcher$ExtClassLoader

应用类加载器(Application ClassLoader)

应用类加载器是应用程序中默认的类加载器,可以加载 CLASSPATH 变量指定目录下的 jar,由 sun.misc.Launcher$AppClassLoader 实现。

并且一般情况下,我们编写的 Java 应用的类,都是使用该类加载器完成加载的。

ClassLoader appLoader = ClassLoader.getSystemClassLoader();
System.out.println(appLoader); // 输出 sun.misc.Launcher$AppClassLoader

类加载器抽象类 ClassLoader

在 Java 中存在一个类加载器抽象类 ClassLoader,大多数类加载器都是通过继承这个类来实现的类加载功能。以下是 ClassLoader 类的关键部分代码:

public abstract class ClassLoader {

    /*
     * 类加载器的父加载器
     */
    private final ClassLoader parent;

    /**
     * 根据类的全限定名加载类
     *
     * @param name 类名称
     * @return     加载的Class对象
     * @throws ClassNotFoundException 没有发现指定类异常
     */
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 调用loadClass方法加载类,其中设置resolve=false,表示不立即解析类
        return loadClass(name, false);
    }

    /**
     * 根据类的全限定名加载类
     *
     * @param name    类名称
     * @param resolve 是否解析这个类,true=解析,false=不解析
     * @return 加载的Class对象
     * @throws ClassNotFoundException 没有发现指定类异常
     */
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 检查类是否已经被加载
            Class<?> c = findLoadedClass(name);
            // 如果没有加载过
            if (c == null) {
                // 如果有父类加载器,则委托给父加载器去加载
                // 如果没有父类加载器,则判断 Bootstrap 类加载器是否加载过
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
                // 如果父类加载器都加载失败,则当前类加载器尝试自行加载
                if (c == null) {
                    c = findClass(name);
                }
            }
            // 据 resolve 参数决定是否解析类
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    /**
     * 查找并加载指定名称的类
     *
     * @param name 类名称
     * @return Class对象
     * @throws ClassNotFoundException 没有发现指定类异常
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //1. 根据传入的类名,到在特定目录下去寻找类文件,把字节码文件读入内存
        // ...
        //2. 调用 defineClass 将字节数组转成 Class 对象
        return defineClass(buf, off, len);
    }

    /**
     * 将一个 byte[] 转换为 Class 类的实例
     *
     * @param name 类名称,如果不知道此名称,则该参数为 null
     * @param b    组成类数据的字节数组
     * @param off  类数据的起始偏移量
     * @param len  类数据的长度
     * @return Class对象
     * @throws ClassFormatError 类格式化异常
     */
    protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError {
        ...
    }

}

类中定义的常用的类加载相关的方法:

方法名称描述
getParent()返回该类加载器的父类加载器
loadClass(String name)加载指定名称的类,返回 java.lang.Class 实例
findClass(String name)查找指定名称的类,返回 java.lang.Class 实例
findLoadedClass(String name)查找已加载的指定名称的类,返回 java.lang.Class 实例
defineClass(String name, byte[] b, int off, int len)将字节数组转换为一个 Java 类,返回 java.lang.Class 实例
resolveClass(Class c)连接指定的 Java 类

双亲委派模型(Parent Delegation Model)

双亲委派模型 是类加载器的设计模式,其核心思想是:类加载请求由子类加载器向父类加载器逐层委派,直到引导类加载器。

如果父类加载器无法加载,子类加载器才会尝试加载。

如果子类加载器也无法加载该类,就会抛出一个 ClassNotFoundException 异常。

1cf1bb298bddffa9ab84a0691c61bb3f.png

双亲委派机制的作用

我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的 String 类来动态替代 Java 核心 API 中定义的类型,这样会存在非常大的安全隐患。

而双亲委托的方式,就可以避免这种情况,因为 String 已经在启动时就被引导类加载器 (BootstrcpClassLoader) 加载,所以用户自定义的 ClassLoader 永远也无法加载一个用户自己自定义的 String 类,除非你改变 JDK 中 ClassLoader 搜索类的默认算法。

该机制的作用如下。

  • 防止重复加载字节码文件: 将类加载请求先委托给父类,父类加载后子类就不会重复加载该类。所以,双亲委派机制可以防止对某个类重复加载;

  • 防止核心字节码文件被篡改: 一般情况下引导类加载器会先加载 JVM 核心类库,然后其它加载器才会执行,如果其它加载器要加载一个被篡改的核心字节码文件,会将该文件委托给父类加载器,当委托到引导类加载器时,加载器已经加载过该类,就不会对该类进行重复加载。而且就算能被加载,那么加载它的肯定不是相同的类加载器 (不会是引导类加载器),Java 虚拟机中只认可核心类加载器加载的核心类库,所以,双亲委派机制可以防止核心字节码文件被篡改。

  • 简化加载逻辑: 通过委派模式,每个类加载器只需要关注自己负责的那部分类加载逻辑,而不必关心其他类加载器的加载细节,简化了类加载器的实现,降低了系统的复杂度。

自定义类加载器

在某些场景下,标准的类加载器无法满足需求,例如:

  1. 热部署:在 Web 服务器中动态加载或更新类。

  2. 模块隔离:在同一个 JVM 中加载不同版本的类。

  3. 加密解密:加载经过加密的 Class 文件。

默认的类加载器只能加载指定目录下的 Jar 和 Class 文件。

如果需要加载指定位置的类文件并实现一些自定义逻辑,就需要自定义类加载器。

Chaya:如何实现自定义类加载器?

步骤

  1. 继承 java.lang.ClassLoader 类。

  2. 重写 findClass() 方法,通过字节流读取 Class 文件并转换为 Class 对象。

import java.io.*;

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String name) {
        String fileName = name.replace('.', '/') + ".class";
        try (InputStream is = new FileInputStream(fileName);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            int buffer;
            while ((buffer = is.read()) != -1) {
                baos.write(buffer);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

示例说明

  • findClass():从文件系统加载 Class 文件,并将其定义为 Class 对象。

  • defineClass():将字节数组转换为 JVM 可执行的 Class 对象。

为了为保证类加载器都正确实现双亲委派机制,在开发自己的类加载器时,只需要重写 findClass() 方法即可。

当然,如果不想使用双亲委派机制时,就需要重写 loadClass() 方法。

打破双亲委派模型

有时为了实现特殊功能,我们需要打破双亲委派模型,例如:

  1. 热部署框架:Tomcat、Spring Boot 使用自定义类加载器加载和卸载 Web 应用。

  2. SPI(Service Provider Interface)机制:JDBC 驱动等需要通过 线程上下文类加载器 来加载用户实现的接口。

最后(Ending)

最后,顺带给大家介绍下,我的新书《Redis 高手心法》。

f5880a0dac5050e60c8a1b1558020d53.jpeg

原价 100 元,现在只需要 50。


c4d676b6585cdb946986002a988582f8.png

作者简介

《Redis 高手心法》 作者,InfoQ 签约作者、51CTO Top 红人。拥有 10 年互联网工作经验,作为后端架构师,擅长 Redis、Tomcat、Spring、Kafka、MySQL 技术,对分布式微服务架构有深入了解。

528a2dc70a6ddd73659ae1c81c894f8f.png

点击卡片,关注我,学技术

往期推荐

面试官拷打:Redis 高可用篇章中面试最常见的 6 个问题!

Redis 7.0 深度探秘:List 数据结构原理与实战指南

重生之从零设计 MySQL 架构

重生之MySQL SQL 执行的 7 大关键步骤,解锁新技能

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

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

相关文章

视频监控汇聚平台Liveweb视频安防监控实时视频监控系统操作方案

Liveweb国标GB28181视频平台是一种基于国标GB/T28181协议的安防视频流媒体能力平台。它支持多种视频功能&#xff0c;包括实时监控直播、录像、检索与回看、语音对讲、云存储、告警以及平台级联等功能。该平台部署简单、可扩展性强&#xff0c;支持全终端、全平台分发接入的视频…

Docker-Compose环境变量

Docker-Compose环境变量 背景配置文件修改docker-compose.yml在服务内部使用环境变量重新构建容器补充 背景 现状是通过Docker-Compose配置管理系统的各个容器服务、因为是微服务架构所以配置文件很多、但是例如数据库、redis、kafka等配置都是同一份但是需要在多个配置文件做…

cocotb pytest

打印python中的print &#xff0c; 应该使用 pytest -s pytest --junitxmltest_report.xml --htmlreport.html

openEuler yum 设置国内镜像

查看openEuler系统信息 cat /etc/os-release可以看到详细系统版本如下 NAME"openEuler" VERSION"24.09" ID"openEuler" VERSION_ID"24.09" PRETTY_NAME"openEuler 24.09" ANSI_COLOR"0;31"系统使用的版本是24.0…

电脑鼠标箭头一直闪烁怎么回事?原因及解决方法

电脑鼠标箭头不停闪烁&#xff0c;很多用户都曾遇到过&#xff0c;就是点击也无法点击&#xff0c;只能看到箭头一直闪动。造成这种故障的原因有很多&#xff0c;可能是硬件、软件或系统的问题。本文将介绍电脑鼠标箭头不停闪烁的可能原因和相应的解决方法&#xff0c;帮助大家…

【开源】A064—基于JAVA的民族婚纱预定系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

Qt6.8 QGraphicsView鼠标坐标点偏差

ui文件拖放QGraphicsView&#xff0c;src文件定义QGraphicsScene赋值给图形视图。 this->scene new QGraphicsScene();ui.graph->setScene(this->scene);对graphicview过滤事件&#xff0c;只能在其viewport之后安装&#xff0c;否则不响应。 ui.graph->viewport…

TxT360: 一个大规模、高质量、多源融合的数据集,专为预训练大型语言模型设计。

2024-10-04, LLM360项目团队创建了TxT360数据集&#xff0c;这个数据集通过整合多种数据源&#xff0c;为预训练大型语言模型提供了丰富的训练材料&#xff0c;具有重要的研究和应用价值。 数据集地址&#xff1a;TxT360|预训练语言模型数据集|预训练数据集 一、研究背景&…

计算机网络-GRE基础实验二

前面我们学习了GRE隧道的建立以及通过静态路由指向的方式使得双方能够网络互联&#xff0c;但是通过静态路由可能比较麻烦&#xff0c;GRE支持组播、单播、广播因此可以在GRE隧道中运行动态路由协议使得网络配置更加灵活。 通过前面的动态路由协议的学习我们知道动态路由协议都…

asp.net core过滤器应用

筛选器类型 授权筛选器 授权过滤器是过滤器管道的第一个被执行的过滤器&#xff0c;用于系统授权。一般不会编写自定义的授权过滤器&#xff0c;而是配置授权策略或编写自定义授权策略。简单举个例子。 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCo…

Pixtral Large开源:Mistral AI的1240亿参数多模态模型超越GPT-4o等竞争对手

Pixtral Large是什么 Pixtral Large是由法国人工智能初创公司Mistral AI开发的超大多模态模型&#xff0c;拥有1240亿参数&#xff0c;2024年11月18日正式对外发布。它基于Mistral Large 2开发而成&#xff0c;具备1230亿参数的多模态解码器和10亿参数的视觉编码器。这个模型能…

【Diffusion分割】基于先验知识的显式-隐式扩散模型用于医学图像分割

扩散概率模型(DPM)在当前的图像生成任务中取得了无与伦比的成果,最近的一些研究工作将其应用于多个计算机视觉任务中,如图像超分辨率、物体检测等。得益于 DPM 生成细粒度细节的卓越能力,这些研究工作取得了显著的成果。在本文中,提出了一种新的基于 DPM 的生成式医学图像…

双目相机的标定,视差图,深度图,点云生成思路与实现。

该文档记录从双目相机标定到点云生成的所有过程&#xff0c;同时会附上代码。 代码直接能跑。https://github.com/stu-yzZ/stereoCamera 目录 大致思路如下&#xff1a; 一、相机标定 1、相机参数介绍 2、单目相机标定 3、双目相机标定 二、图片畸变矫正 三、极线矫正…

记录一下,解决js内存溢出npm ERR! code ELIFECYCLEnpm ERR! errno 134 以及 errno 9009

项目是个老项目&#xff0c;依赖包也比较大&#xff0c;咱就按正常流程走一遍来详细解决这个问题&#xff0c;先看一下node版本&#xff0c;我用的是nvm管理的&#xff0c;详细可以看我的其他文章 友情提醒&#xff1a;如果项目比较老&#xff0c;包又大&#xff0c;又有一些需…

秒懂:使用js验证hash, content hash , chunk hash的区别

一、使用js验证hash, content hash , chunk hash的区别 1、计算一般的 Hash&#xff08;以简单字符串为例&#xff09; 使用crypto-js库来进行哈希计算&#xff0c;需提前引入npm install crypto-js库。 crypto-js&#xff1a; 是一个JavaScript加密算法库&#xff0c;用于实…

基于MATLAB野外观测站生态气象数据处理分析实践应用

1.本课程基于MATLAB语言 2.以实践案例为主&#xff0c;提供所有代码 3.原理与操作结合 4.布置作业&#xff0c;答疑与拓展 示意图&#xff1a; 以野外观测站高频时序生态气象数据为例&#xff0c;基于MATLAB开展上机操作&#xff1a; 1.不同生态气象要素文件的数据读写与批处理…

Unity 画线(UILineRenderer)

实现 以鼠标点击点作为起点创建UILineRenderer 并记录起点。 GameObject go new GameObject(); go.transform.parent transPaint; go.transform.localPosition Vector3.zero; line go.AddComponent<UILineRenderer>(); line.LineWidth widthLine; line.color col…

D86【python 接口自动化学习】- pytest基础用法

day86 pytest配置testpaths 学习日期&#xff1a;20241202 学习目标&#xff1a;pytest基础用法 -- pytest配置testpaths 学习笔记&#xff1a; pytest配置项 主目录创建pytest.ini文件 [pytest] testpaths./testRule 然后Terminal里直接命令&#xff1a;pytest&#xff…

bash命令缓存导致命令执行失败的问题

1、问题背景 为了修复老版本 vsftpd 的安全漏洞&#xff0c;需要把生产环境上 vsftpd 版本升级到 vsftpd-3.0.5&#xff0c;因为直接使用 rpm 包的方式进行升级还涉及到下层依赖包的升级(生产环境上的依赖包版本不能随意变更&#xff0c;可能会影响其他上层应用)&#xff0c;所…

【设计模式系列】工厂方法模式(二十一)

一、什么是工厂方法模式 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;其核心目的是定义一个创建对象的接口&#xff0c;但让实现这个接口的子类来决定实例化哪一个类。工厂方法模式让类的实例化推迟到子类中进行&#xff0c;…