【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 {@Overrideprotected 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 {@Overrideprotected 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) {// 这里可以根据实际情况从文件、网络等地方读取字节码数据// 为了简化示例,我们直接返回nullreturn null;}
}

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

图片

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11.2 Linux串口驱动框架

tty 驱动程序框架 tty 驱动程序从下往上分别是设备驱动层、行规程、终端虚拟化、TTY I/O层&#xff0c;它们的功能如下&#xff1a; 设备驱动层&#xff1a;用于驱动设备&#xff0c;如串口、显示器、键盘等。行规程&#xff1a;用于处理控制字符、回显输入数据、缓存输入数据…

vue3-admin-element框架实现动态路由(根据接口返回)

第一步&#xff1a;在src-utils-handleRoutes&#xff0c;修改代码&#xff1a; export function convertRouter(routers) {let array routersrouters []for (let i in array) {for(let s in asyncRoutes){if (array[i].path asyncRoutes[s].path) {routers.push(asyncRout…

java 音乐会售票平台系统Myeclipse开发mysql数据库struts2结构java编程计算机网页项目

一、源码特点 java 音乐会售票平台系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助struts2框架开发mvc模式&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发 环境为TOCAT7.0,Myeclipse8.5开发&#xff0c;数据…

python练习3【题解///考点列出///错题改正】

一、单选题 1.【单选题】 ——可迭代对象 下列哪个选项是可迭代对象&#xff08; D&#xff09;&#xff1f; A.(1,2,3,4,5) B.[2,3,4,5,6] C.{a:3,b:5} D.以上全部 知识点补充——【可迭代对象】 可迭代对象&#xff08;iterable&#xff09;是指可以通过迭代&#xff…

C#,归并排序算法(Merge Sort Algorithm)的源代码及数据可视化

归并排序 归并算法采用非常经典的分治策略&#xff0c;每次把序列分成n/2的长度&#xff0c;将问题分解成小问题&#xff0c;由复杂变简单。 因为使用了递归算法&#xff0c;不能用于大数据的排序。 核心代码&#xff1a; using System; using System.Text; using System.Co…

Node.js基础知识点(二)

一、Node环境安装&#xff08;Windows&#xff09; 1.下载对应的node.js版本:https://nodejs.org/en/download/ 2.下载完成后&#xff0c;双击安装包&#xff0c;开始安装node.js 3.勾选复选框&#xff0c;点击【Next】按钮 4.修改好目录后&#xff0c;点击【Next】按钮 5.此处…

得物商品状态体系介绍

一、得物的商品体系 目前得物的商品分为三种类型&#xff0c;分别是&#xff1a;新品、商品、草稿。但是只有商品是可售卖的&#xff0c;新品和草稿都不是可售卖的。 新品有很多种创建的渠道&#xff0c;商品可以由新品选品通过后由系统自动生成&#xff0c;也可以由运营直接…

WPF 入门教程DispatcherTimer计时器

https://www.zhihu.com/tardis/bd/art/430630047?source_id1001 在 WinForms 中&#xff0c;有一个名为 Timer 的控件&#xff0c;它可以在给定的时间间隔内重复执行一个操作。WPF 也有这种可能性&#xff0c;但我们有DispatcherTimer控件&#xff0c;而不是不可见的控件。它几…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建一个TcpConnection实例 以及 接收客户端数据

#CSDN 年度征文&#xff5c;回顾 2023&#xff0c;赢专属铭牌等定制奖品# 一、主线程反应堆模型的事件添加和处理详解 >>服务器和客户端建立连接和通信流程&#xff1a; 基于多反应堆模型的服务器结构图&#xff0c;这主要是一个TcpServer&#xff0c;关于HttpServer,…

图像分割实战-系列教程11:U2NET显著性检测实战3

&#x1f341;&#x1f341;&#x1f341;图像分割实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 U2NET显著性检测实战1 U2NET显著性检测实战2 U2NET显著性检测实战3 6、上采样操作与REBNCONV def…

酷开科技 | 酷开系统9.2,开启个性化时代

现代人&#xff0c;总喜欢不走寻常路&#xff0c;以彰显自己的不同。酷开系统的个性化推荐就能满足你的这类需求&#xff0c;既能给你想要的内容&#xff0c;又能给你与众不同的体验&#xff01; 想听音乐了&#xff1f;打开酷开系统音乐频道&#xff0c;随机播放为你推荐的歌曲…

IDEA 2023快捷键

1、main | sout | psvm 2、CTALD 复制当前行 3、ALT SHIFT ↕ 可以把当前行代码进行移动 4、CTRLH 类的继承继承结构 5、CTRLF12 类的成员 6、 SHIFTF6 统一修改变量 7、CTRLATLH 方法调用层级 8、ALT1 是否展示左侧菜单 9、ALTinsert 生成一些代码 10、CTRLP 提示参数…