一文弄懂JVM类加载器与双亲委派机制

news2024/9/20 8:46:19

类的加载器完成类的加载环节中的装载阶段的工作(通过一个类的全限定名来获取该类的二进制字节流,且这个动作在虚拟机**外部实现**,即开发者可以决定如何去获取所需的类),且**不会影响后续的链接和初始化阶段,但类的加载器的存在使得类不会卸载**。

类的加载器的意义:

  • 加载器的意义不仅在于类的加载阶段,而且类和类的加载器共同确立其在虚拟机中的唯一性。一个类的加载器就都拥有一个独立的类名称空间,不同名称空间下的类即使从同一个字节码文件中加载依旧不等(equals()、isInstanceof)。
  • 由于可以自定义类的加载器,可以对需要支持类的动态加载或需要对编译后的字节码文件进行加解密操作,或者可以在程序中编写自定义类加载器来重新定义类的加载规则,以便实现类库的动态加载和自定义的处理逻辑。
  • 自定义加载器能够实现应用隔离,例如 Tomcat,Spring等中间件和组件框架都在内部实现了自定义的加载器,并通过自定义加载器隔离不同的组件模块。(这也是Java相比C/C++的优势所在)

3.1 加载器分类

  1. 类的隐式加载与显式加载

class文件的显式加载与隐式加载的方式是指JVM加载class文件到内存的方式。

  • 显式加载:指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)、this.getClass().getClassLoader().loadClass()加载class对象。
  • 隐式加载:则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。
  1. 加载器类别
  • 启动类加载器 Bootstrap Class Loader (引导类加载器)

C++语言实现,虚拟机内部的加载器(程序内无法获取),系统启动时自动加载核心包内的类。

  • 扩展类加载器 Extension Class Loader

Java语言实现,继承自ClassLoader,专门为加载扩展包、具有通用性的类库的加载器。

  • 应用程序加载器 Application Class Loader (系统类加载器)

Java语言实现,继承自ClassLoader,加载用户类路径(Class Path)上的所有类库(即开发者自定义的类都由这个加载器加载),也是默认的加载器。

  • 自定义类加载器

Java语言实现,继承自ClassLoader,用户自定义的类加载器。

说明:**数组**类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是基本数据类型,数组类是没有类加载器的。

3.2 类加载器的双亲委派机制

3.2.1 双亲委派模型

除了顶层的启动类加载器,所有的类加载器都应有父类加载器(所谓的父类加载器不是由继承实现的,更多地体现为上下层的关系)

在这里插入图片描述

3.2.2 双亲委派模型的原理

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。

规定了类加载的顺序是:

(1)先在当前加载器的缓存中查找有无目标类,如果有,直接返回。

(2)判断当前加载器的父加载器是否为空,如果不为空,则调用parent.loadClass(name, false)接口进行加载。

(3)反之,如果当前加载器的父类加载器为空,则调用findBootstrapClassOrNull(name)接口,让引导类加载器进行加载。

(4)如果通过以上3条路径都没能成功加载,则调用findClass(name)接口进行加载。该接口最终会调用java.lang.ClassLoader接口的defineClass系列的native接口加载目标Java类。

注意:双亲委派机制并非是强制的,重写了loadClass()方法将打破该机制,所以为了避免核心库冲突,JDK为核心类库提供了一层保护机制。不管是自定义的类加载器,还是系统类加载器抑或扩展类加载器,最终都必须调用 java.lang.ClassLoader.defineClass(String, byte[], int, int, ProtectionDomain)方法,而该方法会执行**preDefineClass()**接口,该接口中提供了对JDK核心类库的保护,java包下的类库会强制由启动类加载器加载(无法加载自定义的java.lang的重名包)。

3.2.3 双亲委派机制的优劣势

好处:1.带有优先级的层次关系避免了类的重复加载;2.保护程序安全,防止核心API被随意篡改

弊端:顶层的ClassLoader无法访问底层的ClassLoader所加载的类。比如在系统类中提供了一个接口,该接口需要在应用类中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

3.2.4 打破双亲委派机制

由于存在顶层的ClassLoader无法访问底层的ClassLoader所加载的类的弊端,对于一些特殊场合,不得已需要打破双亲委派机制。

  1. Java 1.2 之前的代码

由于Java1.2之前并没有双亲委派机制,所以为了兼容这些代码,需要打破双亲委派机制。

  1. 线程上下文类加载器ContextClassLoader

ContextClassLoader不是一个优雅的设计,但能解决问题。线程带有一个默认的系统类加载器,使得上层的加载器也能逆向使用下层。

  1. 热替换、热部署

在执行中,如果类代码有更新时不重新打包部署,而是优先用一个新的自定义类加载器去加载这个类,然后再连同加载器一起撤掉原有的类。

例子:tomcat

JDK核心jar包还是遵循双亲委派机制,只是对于tomcat自己相关的类文件有特殊的设计,首先检查在子父类加载器中是否已经加载(缓存),如果没有会直接跳到对应的类加载器加载,加载完了再让上层的类加载器加载尚未加载的内容。

系统类加载器加载tomcat的bin目录下的关键jar包,Common类加载器加载tomcat的lib包下的jar包,Catalina类加载器加载容器私有的类文件,webapp无法访问,Shared类加载器加载webapp之间共享的类文件,Webapp类加载器加载的webapp之间隔离。JSP类加载器用于实现热替换,每个JSP文件都有一个加载器。

当应用需要到某个类时,则会按照下面的顺序进行类加载:

1 使用bootstrap引导类加载器加载

2 使用system系统类加载器加载

3 使用应用类加载器在WEB-INF/classes中加载

4 使用应用类加载器在WEB-INF/lib中加载

5 使用common类加载器在CATALINA_HOME/lib中加载

tomcat是个web容器。tomcat类加载机制的设计,使得Java核心包依旧走双亲委派,对于一些公用的类库,用Common类加载器避免重复加载,而不同的webapp则通过Webapp类加载器来实现相互隔离,JSP类加载器则实现了热部署。

在这里插入图片描述

3.3 ClassLoader源码解析

首先是抽象类ClassLoader在java.lang包下,扩展类装载器和应用程序装载器分别对应sun.misc.Launcher类下的AppClassLoader和ExtClassLoader两个内部类。

在这里插入图片描述

首先看ClassLoader

需要关注的属性

private final ClassLoader parent;

记录了其父类加载器,所谓的父子类加载器,也就是在构造器中传入

protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), null, parent);
}

需要关注的方法

  1. public Class<?> loadClass(String name) throws ClassNotFoundException

加载名称为name的类,返回结果为java.lang.Class类的实例。如果找不到类,则返回 ClassNotFoundException 异常。该方法中的逻辑就是双亲委派模式的实现。自定义加载器时不建议重写。

在未确定父类加载器不能加载该类时,会首先不停地向上层的加载器递归,让上层的加载器优先加载,上层加载器均加载失败时才调用findClass()方法尝试自己加载。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                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;
    }
}
  1. protected Class<?> findClass(String name) throws ClassNotFoundException

**查找二进制名称为name的类,编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象,返回结果为java.lang.Class类的实例。**这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass()方法调用。

说明:loadClass()是加载器类默认会继承的方法,而推荐把自定义的加载逻辑放在findClass(),作为定义加载器时需要重写的方法,这样的抽取就是为了保证双亲委派机制的执行

// ClassLoader中关于该方法只是抛了类未找到的异常
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
// 在URLClassLoader中有相关的实现,在自定义加载器时可以直接继承这个类
protected Class<?> findClass(final String name) throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        } catch (ClassFormatError e2) {
                            if (res.getDataError() != null) {
                                e2.addSuppressed(res.getDataError());
                            }
                            throw e2;
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}
  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len)

根据给定的字节数组b转换为Class的实例,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的。这是受保护的方法,只有在自定义ClassLoader子类中可以使用。

  1. protected final void resolveClass(Class<?> c)

链接指定的一个Java类。使用该方法可以使用类的Class对象创建完成的同时也被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。

  1. protected final Class<?> findLoadedClass(String name)

查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。这个方法是final方法,无法被修改。

  1. private final ClassLoader parent

它也是一个ClassLoader的实例,这个字段所表示的ClassLoader也称为这个ClassLoader的双亲。在类加载的过程中,ClassLoader可能会将某些请求交予自己的双亲处理。

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

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

相关文章

《算法笔记》总结No.10——链表

从第10期破例插叙一期单链表的实现&#xff0c;这个东东相当重要&#xff01;考研的同学也可以看&#xff1a;相较于王道考研的伪码不太相同&#xff0c;专注于可以运行。如果是笔试中的伪码&#xff0c;意思正确即可~ 注&#xff1a;博主之前写过一个版本的顺序表和单链表的C实…

谷粒商城实战笔记-56~57-商品服务-API-三级分类-修改-拖拽功能完成

文章目录 一&#xff0c;56-商品服务-API-三级分类-修改-拖拽功能完成二&#xff0c;57-商品服务-API-三级分类-修改-批量拖拽效果1&#xff0c;增加按钮2&#xff0c;多次拖拽一次保存完整代码 在构建商品服务API中的三级分类修改功能时&#xff0c;拖拽排序是一个直观且高效的…

Linux:Linux权限

目录 1. Linux权限的概念 2. Linux权限管理 2.1 文件访问者的分类 2.2 文件类型和访问权限 2.2.1 文件类型 2.2.2 基本权限 2.3 文件权限值的表示方法 2.4 文件访问权限的相关设置方法 2.4.1 chmod 2.4.2 chown 2.4.3 chgrp 2.4.4 umask 3. file指令 4. Linux目…

如何学习EMR:糙快猛的大数据之路(建立整体框架)

目录 初学EMREMR是什么&#xff1f;我的EMR学习故事糙快猛学习法则代码示例: 你的第一个EMR任务学习EMR的深入步骤EMR进阶技巧实用资源推荐常见挑战和解决方案 EMR生态EMR生态系统深度探索1. EMR上的Hadoop生态系统2. EMR Studio3. EMR on EKS 高级EMR配置和优化1. EMR实例集策…

音视频入门基础:PCM专题(3)——使用Audacity工具分析PCM音频文件

音视频入门基础&#xff1a;PCM专题系列文章&#xff1a; 音视频入门基础&#xff1a;PCM专题&#xff08;1&#xff09;——使用FFmpeg命令生成PCM音频文件并播放 音视频入门基础&#xff1a;PCM专题&#xff08;2&#xff09;——使用Qt播放PCM音频文件 音视频入门基础&am…

ICML 2024最佳论文开奖了!今年的热门投稿方向有这些

ICML 2024最近也放榜啦&#xff01;今年共有10篇论文夺得最佳论文奖&#xff0c;包括火爆的Stable Diffusion 3、谷歌VideoPoet以及世界模型Genie。 ICML是国际机器学习顶会&#xff0c;也是CCF-A类学术会议。今年这届顶会一共收到了9473篇论文&#xff0c;其中2610篇被录用&am…

昇思25天学习打卡营第22天|基于MindNLP+MusicGen生成自己的个性化音乐

文章目录 昇思MindSpore应用实践1、MusicGen模型简介残差矢量量化&#xff08;RVQ&#xff09;SoundStreamEncodec 2、生成音乐无提示生成文本提示生成音频提示生成 Reference 昇思MindSpore应用实践 本系列文章主要用于记录昇思25天学习打卡营的学习心得。 1、MusicGen模型简…

Qt基础 | Qt SQL模块介绍 | Qt SQL模块常用类及其常用函数介绍

文章目录 一、Qt SQL模块概述1.Qt sql 支持的数据库2.SQLite 数据库3.Qt SQL 模块的主要类 一、Qt SQL模块概述 Qt SQL 模块提供数据库编程的支持&#xff0c;Qt 支持多种常见的数据库&#xff0c;如MySQL、Oracle、MS SQL Server、SQLite 等。Qt SQL 模块包括多个类&#xff0…

phpstorm配置xdebug3

查看php路径相关信息 php --ini安装xdebug https://www.jetbrains.com/help/phpstorm/2024.1/configuring-xdebug.html?php.debugging.xdebug.configure php.ini 配置 在最后添加&#xff0c;以下是我的配置 [xdebug] zend_extension/opt/homebrew/Cellar/php8.1/8.1.29/p…

安装NVIDIA驱动

一、不升级内核安装NVIDIA驱动 说明: 1、安装NVIDIA驱动,是用来提升AI、图片等算法 2、本人是在centos7.9操作系统安装英伟达T4板卡驱动 操作系统Centos 7.9驱动版本NVIDIA-Linux-x86_64-525.89.02.run操作账号root1.1 关闭nouveau 1、查看nouveau是否关闭 lsmod |grep nouv…

Android 常用调试工具/方法解析

一、内存相关 参考Android内存分析命令_dumpsys meminfo 算出rss-CSDN博客 1、基本概念 1&#xff09;PSS & RSS & USS & VSS a、PSS 概念&#xff1a;全称Proportional Set Size&#xff0c;根据进程实际使用的内存量按照共享比例分配给进程的一种内存度量方…

MySql性能调优05-[sql实战演练]

sql实战演练 行列转换行列式转换第一题【列转行】第二题【列转行】 having的使用找到表中&#xff0c;名字重复的项有数据表employee&#xff0c;包含如下字段id、name、department、age&#xff0c;编写SQL&#xff0c;找到不与其他人同龄的年纪最大的员工的年龄有数据表emplo…

Nacos-2.4.0最新版本docker镜像,本人亲自制作,部署十分方便,兼容postgresql最新版本17和16,奉献给大家了

基于Postgresql数据库存储的nacos最新版本2.4.0,采用docker镜像安装方式 因业务需要,为了让nacos支持postgresql,特意花了两天时间修改了源码,然后制作了docker镜像,如果你也在找支持postgresql的nacos最新版本,恭喜你,你来的正好~ nacos-2.4.0 postgresql的数据库脚本…

C++学习笔记-C++11中的智能指针

1.智能指针介绍 智能指针是C的特性用法&#xff0c;是一个类似指针功能的类对象&#xff0c;其目的是为了更好的管理动态分配的内存&#xff0c;避免出现内存泄漏、悬空指针等问题。C11的标准库里提供了三种智能指针模板类&#xff0c;分别是std::unique_ptr、std::shared_ptr…

vue 两个页面切换, 再回到当前页,还是离开前的数据

1、要保证页面的name 和 建路由的大小写一致 2、页面不用生命周期--activated 调接口刷新

计算机网络八股文(三)

目录 41.为什么每次建立TCP连接时&#xff0c;初始化的序列号都不一样&#xff1f; 42.初始序列号ISN如何随机产生&#xff1f; 43.既然IP层会分片&#xff0c;为什么TCP层需要根据MSS分片呢&#xff1f; 44.TCP第一次握手丢失&#xff0c;会发生什么&#xff1f; 45.TCP第…

一个python脚本解决新版剪映导出字幕收费问题

如果你是希望我能完全解决剪映收费问题&#xff0c;我无法帮你&#xff1b; 两个文件&#xff0c;可生成不带时间线的纯文案&#xff0c;MD 格式&#xff0c;也可以生成带时间线的 SRT 文件。 因为剪映国内版对 JSON 文件进行了加密&#xff0c;所以请选择国际版 Cutcap&#x…

《javaEE篇》--阻塞队列详解

阻塞队列 阻塞队列概述 阻塞队列也是一种队列&#xff0c;和普通队列一样遵循先进先出的原则&#xff0c;但是阻塞队列相较于普通队列多了两项功能阻塞添加和阻塞移除&#xff0c;使得阻塞队列成为一种线程安全的数据结构 阻塞添加&#xff1a;当队列满的时候继续入队就会阻…

电脑虚拟摄像头软件分享|用手机打破电脑摄像头的极限

随着手机摄像头的不断更新迭代&#xff0c;手机已经接近专业电脑摄像头的画质。这让我们可以花费更低的成本获取优质的电脑录像画面。今天小编给大家详细讲解电脑虚拟摄像头的在我们日常生活中的妙用&#xff0c;以及分享几款口碑不错的电脑虚拟摄像头软件。有需要的小伙伴可以…

从业务到数据,大模型应用成功的再思考!

自2022年底OpenAI发布ChatGPT以来&#xff0c;大模型在企业的应用方兴未艾。 大模型必须要结合落地应用&#xff0c;才算是长出手跟脚&#xff0c;真正应用于实际业务场景的解决方案中&#xff0c;配合“大脑”完成任务。从医疗诊断到自动驾驶&#xff0c;从个性化营销到智能客…