Java classLoader

news2025/4/6 13:49:05

 一. 什么是类加载器

类加载器 classLoader

  • 类加载器:负责将.class文件(存储在硬盘上的物理文件)加载到内存中,是类加载器把类的字节码文件加载到内存当中的。

二. 类加载的过程

每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。

 类在什么时候会被加载到内存中呢?

1.是不是在虚拟机一启动的时候,所有的类就加载到内存当中了呢?

  • 其实并不是这样的,类加载的时机有以下六个。

类加载时机:

  1. 创建一个类的实例对象的时候,比如说我要创建Student学生类对象的时候,这个时候才会把Student类对应的字节码文件加载到内存当中。
  2. 调用类的类方法。这里的类方法,就是指静态方法,而静态方法是用类名直接调用的,不需要创建对象,所以在调用静态方法的时候也会加载这个类的字节码文件。
  3. 访问类或者接口的类变量,或者为该类变量赋值。这里的类变量,其实就是指静态变量,静态变量也是用类名直接调用的,此时不需要创建对象,所以在调用静态变量的时候也会加载这个类的字节码文件。
  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class类对象。当使用反射来创建一个类对象的时候,这个类的字节码文件也会加载到内存当中。
  5. 初始化某个类的子类,说白了就是创建子类对象。比如说我要创建一个Student类的对象,那此时就需要把Student它的父类的字节码文件也加载到内存当中。
  6. 直接使用java.exe命令来运行某个主类。那这个就是运行一个类的时候,需要把这个类的字节码文件加载到内存当中。

总结:用到就加载,不用不加载。类是在用到的时候才加载,不用是不加载的。

类加载的过程:

  • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
  • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
  • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针

  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器(静态代码块)和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
  • 这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例。

  1. 加载:

  • 通过一个类的全限定名来获取定义此类的二进制字节流,简答理解就是类加载器首先会通过包名 + 类名,这个就叫做全限定名,通过它来找到这个字节码文件,因为不同的包下有可能会有相同的类名,所以我们是通过包名 + 类名去找的,那找到字节码文件之后,就需要准备用流来进行传输。那因为在Java当中,所有的数据传输都是以流的形式进行传输的。
  • 那加载呢,还会做第二件事情,将这个字节流所代表的静态存储结构转化为运行时数据结构,那这个简单理解就是通过刚刚得到的流把字节码文件去加载到内存当中。那此时,字节码文件已经到内存里面了。
  • 还有第三件事情,在内存中生成一个代表这个类的Class对象,任何类被使用时,系统都会为之建立一个Class对象。那这句话的意思就是当一个类加载到内存之后,是不能随便乱放的,此时虚拟机会创建这个类的Class对象来存储类中对应的内容。
  • 一开始class文件也就是字节码文件,一定时存储在硬盘上的,首先通过包名+类名找到我想要加载的class字节码文件,然后创建一个流,再通过这个流将字节码文件加载到内存当中,但是加载到内存当中之后,你是不能随便乱放的,此时在内存中就要创建一个class对象,用来存储类的成员信息。
  • 以Student为例,在内存当中创建的就是Student类的class对象,在这个对象里面会存储类的成员信息(成员变量以及成员方法)。
  • 总结:当加载完成后,字节码文件已经到内存中了,并且在内存中会创建一个class对象,用来存储类中的成员信息。

 2.验证:

  • 链接阶段的第一步验证,这一阶段为了确保class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。简答理解就是,看以下文件中的信息是否符合虚拟机规范,有没有一些安全隐患,这个就是验证。

3. 准备:

  • 链接的第二步准备,就是负责为类的类变量,也就是静态变量去分配内存空间,并且设置默认初始化值。那简单理解就是在刚刚创建的class对象当中初始化静态变量。
  • 比如说我Student里面有一个静态的变量school学校,那么在内存当中,就相当于在class对象中为school去开辟了一个空间,赋值为null。

4. 解析:

  •  链接的第三步,解析,是将类的二进制数据流中的符号引用替换为直接引用。

  • 当一个字节码文件加载到内存中的时候,虚拟机会创建一个class对象,假设这一个对象的地址值时0x0011,那对象里面会记录类的成员信息,这些是第一步加载做的事情,但是这些成员信息,它是有类型的,假设这里的Student它的成员变量有String类型的name和int类型的age,这里的String是一个引用数据类型,也就是说String它是一个别的类的名字那么在加载Student的时候String这个类有没有加载我不知道,加载到了哪里我也是不知道的。
  • 所以在最初加载Student类的时候,是不知道String这个类是在哪里的,所以这个时候,这里的String它其实是用符号替代的,那这个就叫做符号引用,因为找不到,所以先用符号替代。

  • 假设这里的符号是且这样的一个符号,那么到了现在进行到了解析这一步的时候,此时就会实际的去找String这个类它在哪里,并且会把这个临时的符号变成实际的String的引用,那假设String的class对象它的地址值是0x0022,那就把上面的这个符号变成0x0022,所以呢这个就叫做符号引用变成直接引用。

  • 那么简单理解一下解析这一步,如果本类中用到了其他的类,那么此时就需要找到对应的类就可以了。

5. 初始化

  • 类加载的最后一步初始化,比较专业的解释呢,就是根据程序员通过程序程序制定的主观计划去初始化类变量和其他资源。那简单理解就是给静态变量进行赋值以及其他资源进行初始化。
  • 那假设在Student类当中有静态变量school,它的值为“传智大学”,那此时就会把传智大学这个值赋值给school这个变量,覆盖原来的默认值null,那这个就是初始化要做的事情。

 类加载过程回顾:

加载

 三. 类加载器的分类

  • 绝大部分的Java程序,都会使用的到以下的三种系统提供的类加载器相互配合执行的,在必要时,我们还可以自定义类加载器。
  • 第一个启动(Bootstrap)类加载器,它是虚拟机内置的类加载器,启动类加载器主要加载的是JVM自身需要的类,底层是用C++语言实现的,是虚拟机自身的一部分,当虚拟机启动的时候,它会自动地进行启动。
  • 第二种平台(Platform)类加载器,负责加载JDK中一些特殊的模块。
  • 第三个叫做系统(System)类加载器它也被称之为应用程序类加载器,负责加载用户类路径上所指定的类库。开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader类当中的静态方法getSystemClassLoader()方法可以获取到系统类加载器。 
  • 在一般情况下,我们自己写的大部分代码就是用第三种系统类加载器去加载到内存当中的,而这些类加载器,它都有各自的加载范围,什么样的类,该用哪一种加载器去加载,这个在Java当中已经规定好了,不需要我们自己再写额外的代码去实现了。
  • 需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

四. 理解类加载器的双亲委派模型 / 模式

双亲委派模型 / 模式工作原理

  • 要学习类加载器的双亲委派模型,我们就需要回忆一下我们刚刚学习的三种类加载器,启动类加载器(Bootstrap ClassLoader),平台类加载器(Platform ClassLoader)和系统类加载器(System ClassLoader),其中用的最多的恶就是系统类加载器,那如果说有必要,我们还可以再加入自定义类加载器,那么这些类加载器之间的层次关系,我们称之为类加载器的双亲委派模型。
  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,这个是最基本的一个条件。
  • 比如说自定义类加载器,它的父类就是系统类加载器,而系统类加载器它的父类就是平台类加载器,而平台类加载器的父类是启动类加载器,启动类加载器它是最顶层的,所以它上面就没有父类加载器了。
  • 请注意双亲委派模式中的父子关系并非通常所说的类继承关系,并非我们在代码当中写的extends继承,而是在逻辑上的继承。
  • 假设现在想要用最下面的类加载器去加载一个字节码文件,它首先不会自己去尝试加载,而是把这个加载任务委派给父类的加载器去完成,也就是自定义类加载器会交给系统类加载器,而系统类加载器它还会委托给平台类加载器去完成,那平台类加载器继续委托给最上面的启动类加载器去完成,而启动类加载器是最顶层的了,所以它不会再往上委托了,因此,所有的加载请求最终都会传递到最顶层的启动类加载器当中,所以这个传递委托的关系,我们也认为是逻辑上的继承。那么这些类加载器都有各自的加载范围,当父类加载器无法完成这个加载请求的时候,它就会一层一层的往下返回,启动类加载器会返回给平台加载器,继续返回给系统加载器,最后再返回给自定义类加载器一层一层往下返回,那此时下面的子加载器才会尝试自己去加载我们想要的字节码文件,在这个模型当中,自定义类加载器我们平时用的不都有,最为常见的就是类加载器System ClassLoader。

  • 双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢?

双亲委派模式优势

  • 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子类加载器ClassLoader再加载一次。
  • 其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

 代码验证双亲委派模型:

package com.gch.d11_class_loader;

/**
   获取类加载器
 */
public class ClassLoaderDemo {
    public static void main(String[] args) {
        // 1.获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        // 2.获取系统类加载器的父加载器 - - - 平台类加载器
        ClassLoader platformClassLoader = systemClassLoader.getParent();

        // 3.获取平台类加载器的父加载器 - - - 启动类加载器
        ClassLoader bootstrapClassLoader = platformClassLoader.getParent();

        System.out.println("系统类加载器:" + systemClassLoader); // 系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println("平台类加载器:" + platformClassLoader); // 平台类加载器:sun.misc.Launcher$ExtClassLoader@14ae5a5
        System.out.println("启动类加载器:" + bootstrapClassLoader); // 启动类加载器:null
    }
}

ClassLoader类的两个重要方法:

package com.gch.d11_class_loader;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
   目标:掌握ClassLoader类的两个重要方法
 */
public class ClassLoaderAPI {
    private static String name = null;

    public static void main(String[] args) throws IOException {
        /**
         * 获取系统类加载器
         * @return ClassLoader:返回系统类加载器
         * public static ClassLoader getSystemClassLoader();
         */
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        /**
         * 利用获取到的加载器去加载一个指定的资源文件
         * @Param String name:资源文件的路径
         * @Return InputStream:返回值是一个字节输入流,文件中的数据都在这个流当中
         * public InputStream getResourceAsStream(String name);
         */
        InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

        // 创建属性集集合对象
        Properties properties = new Properties();
        // 加载属性文件中的键值对数据到属性对象properties中去
        properties.load(is);
        // 在类加载器当中,文件一定要建在src这个文件夹下
        System.out.println(properties);
        // 释放资源
        is.close();
    }
}

 

五. 类与类加载器

类与类加载器

在JVM中表示两个class对象是否为同一个类对象存在两个必要条件:

  • 类的完整类名必须一致,包括包名。
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。

也就是说,在JVM中,即使这个两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的,这是因为不同的ClassLoader实例对象都拥有不同的独立的类名称空间,所以加载的class对象也会存在不同的类名空间中

了解class文件的显示加载与隐式加载的概念

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

显示加载指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)this.getClass().getClassLoader().loadClass()加载class对象。

而隐式加载则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。在日常开发以上两种方式一般会混合使用,这里我们知道有这么回事即可。

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

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

相关文章

vue项目Agora声网实现一对一视频聊天Demo示例(Agora声网实战及agora-rtc-vue使用,新增在线预览地址)

最终效果 在线预览地址 一、声网简介---->请查看官网 二、声网注册---->请自行百度(创建音视频连接需要在Agora注册属于您的appid) 三、具体实现视频聊天步骤 1、 实现音视频通话基本逻辑 1、创建对象 调用 createClient 方法创建 AgoraRTCCli…

ELK 日志系统收集K8s中日志

容器特性给日志采集带来的困难 • K8s弹性伸缩性:导致不能预先确定采集的目标 • 容器隔离性:容器的文件系统与宿主机是隔离,导致日志采集器读取日志文件受阻。 日志按体现方式分类 应用程序日志记录体现方式分为两类: • 标准…

最大似然估计法和Zero Forcing算法的思考

文章目录一、Zero Forcing 算法思想二、MMSE三、MIMO检测中 Zero Forcing 算法比 Maximum Likelihood 差的思考本篇文章是学习了B站UP主 乐吧的数学 之后的笔记总结,老师讲的非常好,大家有兴趣的可以关注一波!一、Zero Forcing 算法思想 那…

Linux应用编程(文件IO基础)

1.1、个简单的文件 IO 示例 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(void) {char buff[1024];int fd1, fd2;int ret;/* 打开源文件 src_file(只读方式) */fd1 open("./src_file",…

差分矩阵算法

前言&#xff1a;我们熟悉一维数组的前缀和和差分数组的相关操作和原理&#xff0c;但是对于二维数组也就是矩阵来说&#xff0c;它的差分和前缀和又会有什么不同之处呢&#xff1f;下面我们一起来研究&#xff0c; 1.二维数组的前缀和 首先&#xff0c;我们一般规定二维数组的…

【RocketMQ】主从同步实现原理

主从同步的实现逻辑主要在HAService中&#xff0c;在DefaultMessageStore的构造函数中&#xff0c;对HAService进行了实例化&#xff0c;并在start方法中&#xff0c;启动了HAService&#xff1a; public class DefaultMessageStore implements MessageStore {public DefaultM…

Vue2-黑马(六)

目录&#xff1a; &#xff08;1&#xff09;element-ui search搜索 &#xff08;2&#xff09;element ui Cascader级联选择器 &#xff08;3&#xff09;Router-路由配置 &#xff08;4&#xff09;Router-动态导入 &#xff08;1&#xff09;element-ui search搜索 我…

日撸 Java 三百行day21-22

文章目录说明day21 二叉树的深度遍历的递归实现1. 二叉树的遍历2. 二叉树深度&#xff0c;结点数3. 代码day 22 二叉树的存储1. 思路2.层次遍历代码3.代码说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把…

《程序员面试金典(第6版)》面试题 10.02. 变位词组

题目描述 编写一种方法&#xff0c;对字符串数组进行排序&#xff0c;将所有变位词组合在一起。变位词是指字母相同&#xff0c;但排列不同的字符串。 注意&#xff1a;本题相对原题稍作修改 示例: 输入: ["eat", "tea", "tan", "ate&quo…

Python接口自动化测试实战详解

接口自动化测试是指通过编写程序来模拟用户的行为&#xff0c;对接口进行自动化测试。Python是一种流行的编程语言&#xff0c;它在接口自动化测试中得到了广泛应用。下面详细介绍Python接口自动化测试实战。 1、接口自动化测试框架 在Python接口自动化测试中&#xff0c;我们…

资源覆盖-overlay机制

1. SRO–Static resource overly(静态替换)2. RRO–Runtime resource overlay (运行时替换) 静态 RRO 1.写配置文件 以下代码显示了一个示例 AndroidManifest.xml。 <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"com.exa…

【Redis】Redis基础命令集详解

文章目录【Redis01】Redis常用命令一、基础命令1、ping&#xff08;心跳命令&#xff09;2、get/set&#xff08;读写键值命令&#xff09;3、select&#xff08;切换数据库&#xff09;4、dbsize&#xff08;查看key数量&#xff09;5、flushdb&#xff08;删除当前库中所有数…

用starter实现Oauth2中资源服务的统一配置

一、前言 Oauth2中的资源服务Resource需要验证令牌&#xff0c;就要配置令牌的解码器JwtDecoder&#xff0c;认证服务器的公钥等等。如果有多个资源服务Resource&#xff0c;就要重复配置&#xff0c;比较繁锁。把公共的配置信息抽取出来&#xff0c;制成starter&#xff0c;可…

【案例实践】Python-GEE遥感云大数据分析、管理与可视化技术及多领域案例实践应用

查看原文>>>Python-GEE遥感云大数据分析、管理与可视化技术及多领域案例实践应用 目录 第一章、理论基础 第二章、开发环境搭建 第三章、遥感大数据处理基础 第四章、典型案例操作实践 第五章、输入输出及数据资产高效管理 第六章、云端数据论文出版级可视化 随…

图片懒加载及Vue自定义图片懒加载指令

文章目录监听滚动的方式IntersectionObserver方式自定义图片懒加载vue指令1自定义图片懒加载vue指令2lazyLoadImage.jsmain.js中注册指令组件中使用学习链接&#xff1a;前端必会的图片懒加载vue自定义指令实现图片懒加载监听滚动的方式 img的src先都用一张默认的图片&#xf…

论文推荐:DCSAU-Net,更深更紧凑注意力U-Net

这是一篇23年发布的新论文&#xff0c;论文提出了一种更深、更紧凑的分裂注意力的U-Net&#xff0c;该网络基于主特征守恒和紧凑分裂注意力模块&#xff0c;有效地利用了底层和高层语义信息。 DCSAU-Net 1、架构 DCSAU-Net 的编码器首先使用 PFC 策略从输入图像中提取低级语义…

适用于 Windows 11/1 0电脑的 8 款最佳免费数据恢复软件

在这个数字办公时代&#xff0c;我们总是在电脑前工作&#xff0c;处理海量数据&#xff0c;存储重要文件。然而&#xff0c;系统崩溃、病毒攻击或人为错误都会导致极度绝望的数据丢失。此时&#xff0c;专业的数据备份和恢复工具就是不幸者的救命稻草。因此&#xff0c;这篇文…

深入剖析Compose布局, 一步步教你打造自适应UI界面

理解Compose布局 Compose 是一种基于声明式编程的 Android UI 工具包&#xff0c;它将可组合的 UI 要素视为函数&#xff0c;并使用 Kotlin DSL 进行构建和组合。Compose 还提供了相应的布局系统和一组用于嵌套和组合 UI 要素的基本函数。 Compose 的核心布局系统由两个部分组…

Windows使用Dockers+battery historian踩坑记

1、首先&#xff0c;需要翻墙。 2、然后安装Dockers&#xff0c;网上好多博客说安装Docker Toolbox&#xff0c;我亲测无效&#xff0c;卸载后安装Docker for Windows&#xff0c;安装完成后打开&#xff0c;会提示&#xff1a; Hardware assisted virtualization and data e…

Promise这样理解更简单

一、Promise小白怎么用&#xff1f;从一个故事开始吧 1、先来一段废话故事 您是一名在古老迷失城市中探险的冒险家。您身处一间装饰华丽的房间中&#xff0c;四周布满了古老的壁画和雕塑。您发现有两个通道分别通向不同的方向&#xff0c;分别是&#xff1a;一个黑暗的通道和…