Jvm类加载机制详解---类加载器及双亲委托模型

2023-11-06

前面介绍了类加载的几个过程,实际中这些过程大部分都是由虚拟机本身去执行的,我们没有办法去改变或影响这些过程的执行。但是虚拟机团队将类加载阶段第一步中的”通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块被称为”类加载器”。

1.类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段,对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。更通俗的来说:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。下面先来分析下类加载器的主要方法,
ClassLoader这个抽象类代表了典型的内加载器逻辑,通过分析它的源码可以看到里面主要有下面几个比较重要的方法:

方法 作用
loadClass() 通过传入的全限定类名拿到类对应的二进制字节流然后生成为类生成一个Class对象,这个方法里的逻辑体现了双亲委托模型
defineClass() 接受类的二进制字节流,为类生成一个Class对象,是通过调用native方法实现的
findLoadedClass() 接受需要加载类的名称,通过检查是否存在对应的Class对象来检测类是否已经被加载,这里通过类的全限定名获取Class对象,里面调用的是native方法findLoadedClass0()。
findClass() 在loadClass()中执行完双亲委托查找模型之后,如果仍然没有找到所需要的类,则会调用findClass()方法来进行查找,所以在实现自己的类加载器的时候可以通过重写这个方法实现。

主要的类加载逻辑还是在loadClass()方法里面,下面看下它的源码:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //为每个类维护一个锁对象,确保在多线程情况下不会出现重复加载的情况
        synchronized (getClassLoadingLock(name)) {
            // 先检查是否已经加载过了
            Class c = findLoadedClass(name);
            if (c == null) {//如果没有加载过则进入加载流程
                long t0 = System.nanoTime();
                try {
                    //当前类有父加载器则使用父加载器进行加载
                    if (parent != null) { 
                        c = parent.loadClass(name, false);
                    } else {
                        //否则调用native方法findBootstrapClass进行加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                //如果上述步骤没有找到Class文件,则使用findClass()进行加载,在ClassLoader中这个方法是没有具体实现的
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

如果不深入通过全限定类名查找Class文件和解析二进制数据流得到类的Class对象的过程,整个类加载的过程还是挺简单的:先判断该类是否已经被加载过了,如果没有被加载并且当前加载器有父加载器,则调用父加载器的loadClass()方法来重复这个过程,如果没有父加载器,则意味着当前的加载器是启动类加载器,调用findBootstrapClass的native方法尝试进行加载。如果上面的过程都没有加载成功,则通过findClass()方法尝试进行加载。
下面通过自定义一个类加载器来验证前面提到的类加载器和Class文件确定一个类唯一性的结论:

public class MyClassLoader {

    public static void main(String[] args) throws Exception {

        final ClassLoader classLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream inputStream = getClass().getResourceAsStream(fileName);
                    if (inputStream == null) {
                        return super.loadClass(name);
                    }
                    byte[] bytes = new byte[inputStream.available()];
                    inputStream.read(bytes);
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };

        Object obj = classLoader.loadClass("com.sankuai.lkl.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof com.sankuai.lkl.ClassLoaderTest);
    }
}

基本的逻辑就是通过自定义的类加载器加载一个类获得Class对象,然后通过newInstance()方法获得这个类的一个实例,并且与系统应用程序类加载器加载的类进行类型比较。输出结果如下,可以看到虽然来自同一个Class文件但确实被看成了两个不同的类:

class com.sankuai.lkl.ClassLoaderTest
false
2.双亲委派模型

上面介绍ClassLoader的loadClass()源码的时候就可以看到类加载器在加载一个类的时候是会先委托给它的父加载器进行加载的,这其实就是双亲委托模型,在介绍双亲委托模型更具体的内容之前先来看下类加载器的分类。
从虚拟机的角度来说,有两类不同的类加载器:一种是启动类加载器(BootStrap ClassLoader),使用C++实现是虚拟机的一部分,外部也没法显式调用这个类加载器;
另外一种是所有其他的类加载器,这些加载器都是使用Java语言实现的,独立于虚拟机外部,这些类加载器都继承自java.lang.ClassLoader。如果要更细致的分,可以将类加载器分类以下三类:

名称 作用
启动类加载器(Bootstrap ClassLoader) 这个类加载器负责将存放在<JAVA_HOME>/lib目录或-Xbootclasspath参数所指定的路径中的,并且是被虚拟机识别的类库加载到内存中,启动类加载器是无法被Java程序直接引用的
扩展类加载器(Extension ClassLoader) 这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>/lib/ext目录中或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader) 这个类加载器由sun.misc.Lancher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称之为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义自己的类加载器,则这个加载器就是程序中默认的类加载器。

除了这三类加载器,我们还可以定义自己的类加载器,这些类加载器是相互配合工作的,他们大致的关系如下图:
这里写图片描述
这里展示的层次关系其实就是双亲委托模型,在这个模型里面除了最顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,这里的父子关系一般是通过组合实现的。
双亲委托模型其实是通过ClassLoader的loadClass()方法实现的,上面已经看过它的源码了,这里在归纳下它大致的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父加载器进行加载,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己进行加载(在findClass()中实现自己查找的逻辑)。

最后需要注意的是双亲委托模型只是一种约定而已,并非强制性的约束;所以在很多情况会对其进行破坏来实现一些特别的功能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Jvm类加载机制详解---类加载器及双亲委托模型 的相关文章

随机推荐

  • C语言,实现字符串排序

    实现字符串排序 include
  • leaftlet 点击事件与取消事件

    var layerNear var mapClick function map on click getRange var getRange function e e latlng地图上点击的点 layerNear L marker e l
  • js 数组遍历的几种方式

    js数组 表示的是有序的数据集合 是一种特殊的对象 对象是无序的数据结合 for循环 for in for each for of es6中数组实例的keys values entries map everyvery等函数 1 for循环
  • [Pyhon大数据分析] 二.PyEcharts绘制全国各地区、某省各城市地图及可视化分析

    思来想去 虽然很忙 但还是挤时间针对这次YQ写个Python大数据分析系列博客 包括网络爬虫 可视化分析 GIS地图显示 情感分析 舆情分析 主题挖掘 威胁情报溯源 知识图谱 预测预警及AI和NLP应用等 希望该系列线上远程教学对您有所帮助
  • win下C++通过Clion部署yolov5——libtorch+yolov5

    libtorch yolov5 一 环境配置 二 下载官网例子 三 测试 3 1 创建项目 3 2 cmakelist txt编写 3 3 运行测试 一 环境配置 需要配置libtorch OpenCV 此处参考博文 clion配置libt
  • 3D扫描技术概览

    3D扫描技术概览 复制链接 楼主 eseedo 发表于 2016 11 22 17 14 26 408 0 只看该作者 内容概要 1 使
  • 黑马程序员 JAVA学习笔记 ——— 多线程

    android培训 java培训 期待与您交流 首先 先介绍一下 熟悉的进程 按下 ctrl alt del就可以看到进程这一选项卡 进程是一个正在执行中的程序 每个进程执行都有一个执行顺序 该顺序是一个执行路径 或叫做一个控制单元 而今天
  • Scala基础语法之Trait详解

    Scala系列学习笔记 Scala概述与开发环境配置 Scala基础学习之运算符 Scala基础学习之for循环和while循环 一文掌握scala中的方法和函数 Scala基础 类和对象 访问修饰符和构造器 Scala的继承和抽象类 本章
  • database Derby initial

    surf the site http db apache org derby derby downloads html you ll get more but first is download the lastest Derby derb
  • Linux 网络通讯 : smbd 命令详解

    smbd命令用于Samba服务器程序 smbd为Samba服务器程序 可分享文件与打印机等网络资源供Windows相关的用户端程序存取 语法 1 smbd aDhoP d lt 排错层级 gt i lt 范围 gt l lt 记录文件 gt
  • FPGA(三)——基于FPGA的SPI通讯协议实现

    一 SPI通讯基本原理 1 SPI通讯介绍 SPI Serial Perripheral Interface 串行外围设备接口 是 Motorola 公司推出的一种同步串行接口技术 SPI 总线在物理上是通过接在外围设备微控制器 PICmi
  • Docker快速安装RabbitMQ服务

    Docker快速安装RabbitMQ服务 快速开始 bin bash 建议保存为start sh脚本执行 docker run d hostname my rabbit name some rabbit restart always p 1
  • Java 基础入门篇(一):Java 概述

    文章目录 一 Java 概述 二 Java 的产品 JDK 2 1 JDK 安装 2 2 Java与 Javac 介绍 2 3 Java 程序的开发步骤 三 Java 程序的执行原理 四 JDK 的组成 五 Java 的跨平台工作原理 一
  • Solidity transfer,call和send 的区别

    address transfer throws on failure forwards 2 300 gas stipend not adjustable safe against reentrancy should be used in m
  • SDF文件【简要说明】

    SDF Standard Delay Format 标准延时格式文件 常用延迟反标注 该文件包含了仿真用到的所有 IOPATH INTERCONNECT的延时 线延时 INTERCONNECT fsm block U27 Q fsm blo
  • 2020-10-10

    闭包和装饰器 1 高阶函数 接收函数作为参数是高阶函数 将函数作为返回值返回的函数就是高阶函数 2 匿名函数 lambda函数 无名函数 语法 lambda 参数列表 表达式 filter 函数 过滤列表 第一个参数 函数 第二个参数 序列
  • Java设计模式(十四)—— 模板方法模式

    模板方法模式是指定义一个操作中算法的骨架 而将一些步骤延迟到子类中 模板方法使子类可以不改变一个算法的结构 即可重定义该算法的某些特定步骤 适合模板方法模式的情景如下 编制一个通用算法 将某些步骤的具体实现留给子类来实现 需要重构代码 将各
  • 对Linux svn保存的明文密码加密

    需求来源 随着GitHub GitLab的兴起 svn已经渐渐的没落了 从公司当初的源代码管理服务器 逐渐演变成公司的ftp服务器 最近需要部署gitlab的CI单元测试模块 而软件版本都在svn上有备份 我就希望从代码的提交 gt 到版本
  • Python统计文本数字,字母,单词量

    统计一百万位圆周率中数字0 9各自的数量 统计一本书中字母a z各自的数量 统计一本书共有多少个单词 含重复的单词 和单词量 不含重复的单词 import string class CountNums 求txt文本中数字或字母的数量 def
  • Jvm类加载机制详解---类加载器及双亲委托模型

    前面介绍了类加载的几个过程 实际中这些过程大部分都是由虚拟机本身去执行的 我们没有办法去改变或影响这些过程的执行 但是虚拟机团队将类加载阶段第一步中的 通过一个类的全限定名来获取描述该类的二进制字节流 这个动作放到虚拟机外部去实现 以便让应