【JVM】类加载器ClassLoader

图片

一、简介

在Java中,类加载器(ClassLoader)是一个关键的组件,它负责将字节码文件加载到内存并转换成Java类。Java的类加载器主要可以分成两类:系统提供的和由Java应用开发人员编写的。Java开发者可以根据需要创建自己的类加载器。所有的类加载器都继承自抽象类ClassLoader。当JVM需要加载一个类时,它会首先请求父类加载器去尝试加载这个类,如果父类加载器无法找到相应的类或者该类的字节码文件,那么该请求就会传递给子类加载器,依此类推,直到某个类加载器找到了相应的字节码文件为止。

总的来说,Java中的类加载器是一个重要的环节,它确保了Java程序能够正确地运行和使用各种不同类型的类。

二、加载器分类

从JVM的角度看,类加载器可以分为两种:

  • 引导类加载器(启动类加载器 Bootstrap ClassLoader);

  • 其他所有类加载器,这些类加载器由 java 语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader。

- 引导类加载器
  - 扩展类加载器(Extension ClassLoader)
    - 应用程序类加载器(Application ClassLoader)
      - 自定义类加载器

从 java 开发人员的角度来看,类加载器就应当划分得更细致一些,自 JDK1.2 以来 java 一直保持者三层类加载器:

1. 引导类加载器(启动类加载器 BootStrap ClassLoader)

  • 这个类加载器使用 C/C++语言实现,嵌套在 JVM 内部.它用来加载 java 核心类库.并不继承于 java.lang.ClassLoader 没有父加载器。

  • 负责加载扩展类加载器和应用类加载器,并为他们指定父类加载器。

  • 出于安全考虑,引用类加载器只加载存放在<JAVA_HOME>\lib 目录,或者被-Xbootclasspath 参数锁指定的路径中存储放的类。

2. 扩展类加载器(Extension ClassLoader)

  • Java 语言编写的,由 sun.misc.Launcher$ExtClassLoader 实现;

  • 派生于 ClassLoader 类;

  • 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也会自动由扩展类加载器加载。

3. 应用程序类加载器(系统类加载器 Application ClassLoader)

  • Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现;

  • 派生于 ClassLoader 类;

  • 加载我们自己定义的类,用于加载用户类路径(classpath)上所有的类;

  • 该类加载器是程序中默认的类加载器;

  • ClassLoader 类 , 它 是 一 个 抽 象 类 , 其 后 所 有 的 类 加 载 器 都 继 承 自 ClassLoader(不包括启动类加载器)。

图片

自定义类加载器的实现通常继承自java.lang.ClassLoader类或其子类。以下是一个简单的自定义类加载器示例:



import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

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

    private byte[] loadClassData(String className) {
        String fileName = getFileName(className);
        try (InputStream is = getParent().getResourceAsStream(fileName)) {
            if (is == null) {
                return null;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead;
            while ((bytesNumRead = is.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在这个示例中,我们创建了一个名为CustomClassLoader的自定义类加载器,它继承自ClassLoader类。我们重写了findClass方法,该方法接收一个字符串参数name,表示要加载的类的全名。在这个方法中,我们首先调用loadClassData方法来读取类的字节码数据,然后使用defineClass方法将字节码数据转换为Java类。

三、类加载过程

图片

1. 加载

  • 通过类名(地址)获取此类的二进制字节流。

  • 将这个字节流所代表的静态存储结构转换为方法区(元空间)的运行时结构。

  • 在内存中生成一个代表这个类的java.lang.class对象,作为这个类的各种数据的访问入口。

2. 链接

  • 验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致;

    验证文件格式是否一致: class 文件在文件开头有特定的文件标识(字节码文件都以 CA FE BA BE 标识开头);主,次版本号是否在当前 java 虚拟机接收范围内;

    元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java 语言规范的要求,例如这个类是否有父类;是否继承浏览不允许被继承的类(final 修饰的类).....

  • 准备:准备阶段则负责为类的静态属性分配内存,并设置默认初始值;

    不包含用 final 修饰的 static 常量,在编译时进行初始化;

    例如: public static int value = 123;value 在准备阶段后的初始值是 0,而不是 123;

  • 解析:将类的二进制数据中的符号引用替换成直接引用(符号引用是 Class 文件的逻辑符号,直接引用指向的方法区中某一个地址)。

3. 初始化

初始化,为类的静态变量赋予正确的初始值,JVM 负责对类进行初始化,主要对类变量进行初始化。初始化阶段就是执行底层类构造器方法<clinit>()的过程。此方法不需要定义,是 javac 编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来的。

类什么时候初始化:

JVM规定:每个类或者接口被首次主动使用时才对其进行初始化。

  • 通过 new 关键字创建对象;

  • 访问类的静态变量,包括读取和更新;

  • 访问类的静态方法;

  • 对某个类进行反射操作;

  • 初始化子类会导致父类的的初始化;

  • 执行该类的 main 函数;

除了以上几种主动使用,以下情况被动使用,不会加载类:

  • 引用该类的静态常量,注意是常量,不会导致初始化,但是也有意外,这里的常量是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导致类加载,比如:

    public final static int NUMBER = 5 ; //不会导致类初始化,被动使用

    public final static int RANDOM = new Random().nextInt() ; //会导致类加载。

  • 构造某个类的数组时不会导致该类的初始化,比如:

    Student[] students = new Student[10] ;

类的初始化顺序

  • 对static修饰的变量或语句块进行赋值

    如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行;

    如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类;

    顺序是:父类 static –> 子类 static。

四、双亲委派机制

Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要该类时才会将它的 class 文件加载到内存中生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

图片

工作原理:

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请 求委托给父类的加载器去执行;

  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。

  • 如果父类加载器可以完成类的加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制.

  • 如果均加载失败,就会抛出 ClassNotFoundException 异常。

双亲委派优点:

  • 安全,可避免用户自己编写的类替换 Java 的核心类,如 java.lang.String;

  • 避免类重复加载,当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次。

如何打破双亲委派机制

Java 虚拟机的类加载器本身可以满足加载的要求,但是也允许开发者自定义类加载器。

在 ClassLoader 类中涉及类加载的方法有两个,loadClass(String name), findClass(String name),这两个方法并没有被 final 修饰,也就表示其他子类可以重写。

  • 重写 loadClass 方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制, 不推荐)。

  • 重写 findClass 方法 (推荐)。

我们可以通过自定义类加载重写方法打破双亲委派机制, 再例如 tomcat 等都有自己定义的类加载器。


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

    private byte[] loadClassData(String className) {
        // 这里可以根据实际情况从文件、网络等地方读取字节码数据
        // 为了简化示例,我们直接返回null
        return null;
    }
}

在这个示例中,MyClassLoader继承自ClassLoader,并重写了findClass方法。当JVM需要加载一个类时,它会首先请求父类加载器去尝试加载这个类,如果父类加载器无法找到相应的类或者该类的字节码文件,那么该请求就会传递给MyClassLoader,然后调用MyClassLoader的findClass方法。在findClass方法中,我们首先调用loadClassData方法来获取类的字节码数据,然后将这些字节码数据转换成Java类。

图片

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

54、Softmax 分类器以及它的底层原理

下面开始介绍最后一个算法softmax。在前面介绍全连接算法或其他文章中,或多或少也提到了softmax。 在分类网络里,softmax的作用主要是将模型的原始输出映射到 0~1之间的概率分布。很多时候对于我们初学者而言,只知道softmax可以做概率映射,但并不了解它内部的原理是如何完…

【Linux Shell】8. test 命令

文章目录 【 1. 数值测试 】【 2. 字符串测试 】【 3. 文件测试 】 Shell中的 test 命令用于检查某个条件是否成立&#xff0c;它可以进行数值、字符和文件三个方面的测试。 【 1. 数值测试 】 参数作用-eq等于则为真-ne不等于则为真-gt大于则为真-ge大于等于则为真-lt小于则…

2023年广东省网络安全A模块(笔记详解)

模块A 基础设施设置与安全加固 一、项目和任务描述&#xff1a; 假定你是某企业的网络安全工程师&#xff0c;对于企业的服务器系统&#xff0c;根据任务要求确保各服务正常运行&#xff0c;并通过综合运用登录和密码策略、流量完整性保护策略、事件监控策略、防火墙策略等多…

TF-IDF(Term Frequency-Inverse Document Frequency)算法 简介

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种用于信息检索和文本挖掘的常用算法。它用于评估一个词对于一个文档集合中某个文档的重要性。 这个算法的基本思想是&#xff1a;如果一个词在一个文档中频繁出现&#xff0c;并且在整个文档集合…

poium测试库之JavaScript API封装原理

为什么要封装JavaScript的API&#xff1f; 因为有些场景下Selenium提供的API并不能满足我们需求。比如&#xff0c;滑动浏览滚动条&#xff0c;控制元素的显示/隐藏&#xff0c;日历控件的操作等&#xff0c;都可以通过JavaScrip实现&#xff0c;而且Selenium为我们提供了 exe…

QT上位机开发(网络程序界面开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 传统的上位机对接方式还是以232、485、can为主&#xff0c;随着网络的发展&#xff0c;越来越多的设备都是以网络进行通信的。毕竟相比较之前&…

分布式(7)

目录 31.基于Zookeeper如何实现分布式锁&#xff1f; 32.什么是ACID&#xff1f; 33.什么是分布式的XA协议&#xff1f; 34.什么是2PC&#xff1f; 35.什么是3PC&#xff1f; 31.基于Zookeeper如何实现分布式锁&#xff1f; 顺序节点 创建一个用于发号的节点“/test/lock…

uniapp vue2 车牌号输入组件记录

uniapp vue2 车牌号输入案例记录 组件如图 直接上代码 1.html <template><view><view class"plate" :class"{show: show}"><view class"itemFirst flex-d"><view class"item item1" click"handl…

自定义标记

章节目录&#xff1a; 一、概述二、使用自定义标记三、注册自定义标记3.1 创建文件3.2 修改文本编码格式 四、执行测试五、结束语 一、概述 pytest 可以支持自定义标记&#xff0c;自定义标记可以把一个 web 项目划分多个模块&#xff0c;然后指定模块名称执行。譬如我可以标明…

Java基本语法

第一章 Java基本语法 1. Java程序剖析1.1 Java代码的基本格式1.2 包、import1.3 类1.4 main()方法1.5 方法1.6 标识符1.7 关键字1.8 修饰符1.9 程序块1.10 语句1.11 Java代码的注释 2. 常量与变量2.1 常量2.2 变量2.2 变量的分类2.2.1 成员变量2.2.2 局部变…

中国文化文物和旅游统计年鉴,数据含pdf、excel等格式,文本形式呈现,可预览数据

基本信息. 数据名称: 中国旅游统计年鉴 数据格式: pdf、xls不定 数据时间: 2012-2020年 数据几何类型: 文本 数据坐标系: —— 数据来源&#xff1a;文化和旅游部、网络公开数据 原名为《中国旅游统计年鉴》2020年后更名为《中国文化文物和旅游统计年鉴》&#xff…

实验笔记之——基于COLMAP的Instant-NGP与3D Gaussian Splatting的对比

之前博客进行了COLMAP在服务器下的测试 实验笔记之——Linux实现COLMAP-CSDN博客文章浏览阅读794次&#xff0c;点赞24次&#xff0c;收藏6次。学习笔记之——NeRF SLAM&#xff08;基于神经辐射场的SLAM&#xff09;-CSDN博客NeRF 所做的任务是 Novel View Synthesis&#xf…

史上最牛逼的fiddler抓包操作,【工具】Fiddler使用教程

eb调试工具之一&#xff0c; 它能记录所有客户端和服务器的http和https请求。允许你监视、设置断点、甚至修改输入输出数据。Fiddler包含了一个强大的基于事件脚本的子系统&#xff0c;并且能使用.net语言进行扩展。换言之&#xff0c;你对HTTP 协议越了解&#xff0c;你就能越…

【DevOps-07-2】Sonarqube基本使用

一、简要说明 Sonar Qube的使用方式很多&#xff0c;Maven可以整合&#xff0c;也可以采用sonar-scanner的方式&#xff0c;再查看Sonar Qube的检测效果 Sonarqube集成在Maven实现代码检测使用sonar-scanner客户端的方式 二、Sonarqube管理后台安装中文插件 1、登录Sonarqube管…

案例253:基于微信小程序的懂球短视频管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SpringBoot JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder …

无人机发动机,预计到2025年将保持相对稳定

随着无人机技术的不断发展&#xff0c;无人机应用领域越来越广泛&#xff0c;市场需求也不断扩大。而无人机发动机是无人机的核心部件之一&#xff0c;市场前景也十分广阔。全球市场分析&#xff1a; 目前&#xff0c;全球无人机发动机市场主要分为四类&#xff1a;燃气涡轮发动…

【自学笔记】01Java基础-09Java关键字详解

介绍java&#xff08;基于java11&#xff09;中所有关键字&#xff0c;以及主要重要的关键字详解。 1 Java 11中的关键字&#xff1a; 1.1 类型声明与变量定义 boolean&#xff1a;声明布尔类型变量&#xff0c;只有两个可能值 true 或 false。byte&#xff1a;声明一个8位有…

公共用例库计划--个人版(二)主体界面设计

1、任务概述 计划内容&#xff1a;完成公共用例库的开发实施工作&#xff0c;包括需求分析、系统设计、开发、测试、打包、运行维护等工作。 1.1、 已完成&#xff1a; 需求分析、数据库表的设计&#xff1a;公共用例库计划–个人版&#xff08;一&#xff09; 1.2、 本次待…

vmware安装centos 7.6 操作系统

vmware安装centos 7.6 操作系统 1、下载centos 7.6 操作系统镜像文件2、安装centos 7.6操作系统3、配置centos 7.6 操作系统3.1、配置静态IP地址 和 dns3.2、查看磁盘分区3.3、查看系统版本 1、下载centos 7.6 操作系统镜像文件 这里选择 2018年10月发布的 7.6 版本 官方下载链…

33--反射

1、反射(Reflection)的概念 1.1 反射的出现背景 Java程序中&#xff0c;所有的对象都有两种类型&#xff1a;编译时类型和运行时类型&#xff0c;而很多时候对象的编译时类型和运行时类型不一致。 Object obj new String("hello"); obj.getClass(); 例如&#xf…