当前位置: 首页 > news >正文

类加载机制及双亲委派模型

一、引言

二、类加载流程

 1. 加载

2. 连接 

        2.1 验证

         2.2 准备

        2.3 解析

3. 初始化

三、类加载器

    类加载器的类型

     双亲委派模型

打破双亲委派模型

双亲委派模型优点

一、引言

在 Java 的运行机制中,类加载是一个至关重要的环节。它不仅决定了 Java 程序的动态性和灵活性,还为 Java 的安全性和稳定性提供了基础保障。类加载机制的核心在于类加载器(ClassLoader),它负责将字节码文件加载到 Java 虚拟机(JVM)中,并将其转换为可执行的类对象。这一过程不仅涉及到类的加载、解析和初始化,还通过双亲委派模型确保了类加载的安全性和一致性。

本文将深入探讨 Java 类加载的完整流程。同时,我将介绍双亲委派模型的原理及其优势,以及如何通过自定义类加载器打破这一模型以满足特定需求。希望读者能掌握如何在实际开发中利用这一机制实现灵活的类加载策略。

二、类加载流程

首先我们要知道类加载的流程,

类加载的过程主要分三步:加载 ——连接——初始化

而连接这一步又分了三步:验证——准备——解析

这里我们绘制了一个图片描述了类加载的流程。

 1. 加载

 这是类加载器的第一步

首先我们通过全限定名获取到当前类的二进制流;

然后我们把字节流代表的静态存储结构转换为方法区的运行时数据结构(类的结构信息,常量池,字段信息,静态变量等等)Classloader通过defineClass将字节流数据转化class对象。defineClass只是个入口,该过程是在JVM底层实现。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }

2. 连接 

        2.1 验证

  • 主要是为了确保字节流信息符合.class规范,Java的.class文件开头魔数是0xCAFEBABE,通过他就可以校验是否是Java文件了,满足了魔数还要判断以下内容

  • 元数据:检查元数据是否符合语义,是否有重复字段?不允许继承的类?等等……
  • 字节码验证:检查字节码是否符合规范,类型转化是否正确
  • 符号引用验证:是否引用了不能别的类而权限不正确       

         2.2 准备

        主要是为静态变量分配内存,并设置初始值,如果使用了final修饰还会直接为他附上初值

        2.3 解析

        把符号引用转换成直接引用的过程

3. 初始化

             初始化是类加载的最后一步,执行了类的初始化代码<clinit>()(编译后自动生成)

    包括静态变量的赋值,静态代码块,很经典的一道题就是父子类的代码块顺序问题,答案如下

    1. 父类的静态变量赋值。

    2. 父类的静态代码块执行。

    3. 当前类的静态变量赋值。

    4. 当前类的静态代码块执行。

    类加载触发初始化的条件:

            初始化阶段的执行是类加载机制的一部分,但并不是所有类加载都会触发初始化。根据 Java 规范,以下情况会触发类的初始化:

    1. 创建类的实例

      • 通过 new 关键字创建类的实例。

      • 通过反射创建类的实例。

      • 通过克隆(clone)创建类的实例。

    2. 调用静态方法或访问静态字段

      • 调用类的静态方法。

      • 访问类的静态字段(除了通过 Class.forName 加载类但不执行初始化的情况)。

    3. 子类初始化

      • 如果子类初始化,则父类也会被初始化。

    4. JVM启动时加载的类

      • JVM 启动时加载的主类(如 public static void main(String[] args) 所在的类)。

     

    三、类加载器

            类加载器的主要功能就是加载字节码到JVM中去,他赋予了Java类动态加载到JVM并执行的能力。类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。每个 Java 类都有一个引用指向加载它的 ClassLoader

        类加载器的类型

    • BootstrapClassLoader:启动类加载器,最顶级加载器,没有父类,获取父类得到他的时候会返回null,是由c++实现的。
    • ExtensionClassloader:扩展类加载器,加载jar包以及系统变量指定路径下的类
    • AppClassLoader:应用程序类加载器,面向用户加载器,加载classpath下的所有jar包和类

            我们还可以加入自定义类加载器,暂且不提。

         双亲委派模型

            既然我们有了这么多类加载器,那么我们使用的时候会如何选择呢?

            这就提到了我们的双亲委派模型:每次查找的时候,每一级ClassLoader 都会把搜索类或者资源的任务委托给父类加载器(也是防止重复加载),如果父类加载器找不到再给到下一级

    执行流程:这里我们贴上源码

    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;
            }
        }

    可以看到我们首先通过本地方法findLoadedClass查找是否被加载过

    如果没有被加载过再进入下一步,如果父类加载器不为空,就调用父类加载器的loadClass

    如果没有父类加载器(parent == null),则尝试通过根加载器加载类(通过 findBootstrapClassOrNull(name) 方法)。

    如果父类加载器(或引导类加载器)无法加载类,则调用当前类加载器的 findClass(String name) 方法来加载类。

    基本流程也就结束了

    打破双亲委派模型

    自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

    双亲委派模型优点

    避免了类的重复加载,父加载器加载过了子类不会加载。

    保证了Java核心的API不被篡改,不然我自己写一个Object类JVM直接使用了,但是我有双亲委派,他会一直找到根加载器找到核心API


    写到这里我们就了解了JVM的类加载机制已经双亲委派模型,希望各位大佬多多指教!

    相关文章:

  • 使用 SDKMAN! 在 Mac(包括 ARM 架构的 M1/M2 芯片)安装适配 Java 8 的 Maven
  • CF 137B.Permutation(Java 实现)
  • 审计费用差10倍?项目规模如何影响报价
  • 【ISO 14229-1:2023 UDS诊断全量测试用例清单系列:第十五节】
  • P5693 EI 的第六分块 Solution
  • Transformer 模型介绍(三)——自注意力机制 Self-Attention
  • 第二章:12.6 偏差或方差与神经网络
  • Sentinel 源码深度解析
  • 136,【3】 buuctf web [极客大挑战 2020]Roamphp4-Rceme
  • vue若依框架dicts中字典项的使用:表格展示与下拉框示例
  • 《AI大模型开发笔记》Open-R1:对 DeepSeek-R1 的完全开源再现(翻译)
  • 静力触探数据智能预处理(6)
  • JavaScript 内置对象-Math对象
  • (学习总结23)Linux 目录、通配符、重定向、管道、shell、权限与粘滞位
  • [8-2-2] 队列实验_多设备玩游戏(红外改造)_重录
  • IWC万国表:源自瑞士的精密制表艺术(中英双语)
  • 第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式
  • skywalking实现原理
  • unity学习39:连续动作之间的切换,用按键控制角色的移动
  • 编程技巧(基于STM32)第一章 定时器实现非阻塞式程序——按键控制LED灯闪烁模式
  • 最新研究:新型合成小分子可“精准杀伤”癌细胞
  • 体坛联播|C罗儿子完成国家队首秀,德约结束与穆雷合作
  • 文学花边|对话《借命而生》原著作者石一枫:我给剧打90分
  • 浙江一民企拍地后遭政府两次违约,“民告官”三年又提起民事诉讼
  • 一热就出汗 VS 热死都不出汗的人,哪个更健康?
  • 公示!17个新职业、42个新工种亮相