深入分析ClassLocader工作机制

文章目录

    • 一、ClassLoader简介
      • 1. 概念
      • 2. ClassLoader类结构分析
    • 二、ClassLoader的双亲委派机制
    • 三、Class文件的加载流程
      • 1. 简介
      • 2. 加载字节码到内存
      • 3. 验证与解析
      • 4. 初始化Class对象
    • 四、常见加载类错误分析
      • 1. ClassNotFoundException
      • 2. NoClassDefFoundError
      • 3. UnsatisfiledLinkError
      • 4. ClassCastException
      • 5. ExceptionInInitializerError
    • 五、自定义ClassLoader的优势

一、ClassLoader简介

1. 概念

ClassLoader顾名思义就是类加载器,负责将Class加载到JVM中。事实上,ClassLoader除了能够将Class加载到JVM意外以外,还有一个重要的作用就是审查每个类应该由谁加载,它是一种父优先的等级加载机制。此外,ClassLoader除了上述的两个作用外还有一个任务就是将Class字节码重新解析成JVM统一要求的对象格式。

2. ClassLoader类结构分析

我们用到ClassLoader时常用下面的几个方法,以及它们的重载方法:

public abstract class ClassLoader {ClassLoader;Class<?> defineClass(byte[],int,int);Class<?> findClass(String);Class<?> loadClass(String);void resolveClass(Class<?>);
}

defineClass方法用来将byte字节流解析成JVM能够识别的Class对象,有了这个方法我们不仅仅可以通过class文件实例化对象,还可以通过其他方式如我们通过网络接收一个类的字节码,拿这个字节码流直接创建类的Class对象形式实例化对象。defineClass通常是和findClass方法一起使用的,我们通过直接覆盖ClassLoader父类的findClass方法来实现类的加载机制,从而取得想要加载类的字节码。然后调用defineClass方法生成类的Class对象,如果你想在类被加载到JVM中时就被链接,那么可以调用另一个resolveClass方法,当然你也可以选择让JVM来解决什么时候才链接到这个类。

如果你不想重新定义加载类的规则,只想在运行时能够加载自己指定的一个类而已,那么你可以用this.getClass().getClassLoader().loadClass("class")调用ClassLoader的loadclass方法可以获取这个类的Class对象,这个loadClass还有重载方法,你统一可以决定在上面时候解析这个类。

二、ClassLoader的双亲委派机制

双亲委派机制(Parent Delegation Mechanism)是Java中的一种类加载机制。在Java中,类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。

这种机制的设计目的是为了保证类的加载是有序的,避免重复加载同一个类。Java中的类加载器形成了一个层次结构,根加载器(Bootstrap ClassLoader)位于最顶层,它负责加载Java核心类库。其他加载器如扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)都有各自的加载范围和职责。通过双亲委派机制,可以确保类在被加载时,先从上层的加载器开始查找,逐级向下,直到找到所需的类或者无法找到为止。

这种机制的好处是可以避免类的重复加载,提高了类加载的效率和安全性。同时,它也为Java提供了一种扩展机制,允许开发人员自定义类加载器,实现特定的加载策略。
在这里插入图片描述
其实Bootstrap ClassLoader并不属于JVM的类等级层次,因为BootStrap ClassLoader并没有遵守ClassLoader的加载规则,另外它并没有子类,ExtClassLoader的父类也不是Bootstrap ClassLoader,我们应用中能取到的顶层父类时ExtClassLoader。

ExtClassLoader和AppClassLoader都位于sun.misc.Launcher类中,它们是Loucher类的内部类。ExtClassLoader和AppClassLoader都继承了URLClassLoader,而URLClassLoader又实现了抽象类ClassLoader,在创建Launcher对象时会首先创建ExtClassLoader,然后将ExtClassLoader作为父加载器创建AppClassLoader对象,而通过Launcher.getClassLoade()方法获取的ClassLoader就是AppClassLoader对象。所以如果Java应用中没有定义其他ClassLoader,那么除了System.getProperty("java.ext.dirs")目录下的类是由ExtClassLoader加载为,其它类都由AppClassLoader来加载。

JVM加载class文件到内存中有两种方式:

  • 隐式加载:所谓隐式加载是不通过在代码里面调用ClassLoader来加载所需要的类,而是铜鼓oJVM来自动加载所需的类到内存的方式。例如:当我们在类中继承或者引用某个类是,JVM在解析当前这个类时发现引用不在内存中,那么自动将这些类加载到内存中。
  • 显式加载:相反的显式加载就是我们在代码中使用ClassLoader类加载一个类的方式

其实这两种方式是混合使用的,例如我们通过自定义的ClassLoader显式加载一个类时,这个类又引用了其他类,那么这些类就是隐式加载的。

三、Class文件的加载流程

1. 简介

下面分析如何将class文件加载到JVM中。ClassLoader加载一个class文件到JVM要经历如下阶段:

在这里插入图片描述

  • 首先找到class文件并把这个文件包含的字节码加载到内存中
  • 链接阶段分为三个步骤,分别是字节码验证、Class类数据结构分析及相应的内存分配和最后符号表的链接
  • 最后是类中静态数据和初始化赋值,以及静态块的执行

2. 加载字节码到内存

findClass()的方法是在ClassLoader实现类中实现的,例如URLClassLoader就实现了该方法,URLClassLoader类通过一个URLClassPath类的帮助取得要加载的class文件字节流,而这个URLClassPath定义了到哪里去找这个class文件,如果找到了这个class文件,再读取它的byte字节流通过调用defineClass()方法创建类对象。

 private final URLClassPath ucp;

再看其构造函数,要指定一个URL数据才能创建URLClassLoader对象,也就是必须要指定这个ClassLoader默认到哪个目录中去查找class文件

  public URLClassLoader(URL[] urls, ClassLoader parent) {super(parent);SecurityManager security = System.getSecurityManager();if (security != null) {security.checkCreateClassLoader();}this.acc = AccessController.getContext();ucp = new URLClassPath(urls, acc);}

在创建URLClassLoader对象时就根据传过来的URL数组中的路径来判断是文件还是jar包,根据路径不同分别创建FileLoader或者JarLoader,或者使用默认的加载器,当JVM调用findClass时由这几个加载器来将class文件加载到内存中。

3. 验证与解析

  • 字节码验证,类装入器对于类的字节码要做许多检测,以确保格式正确、行为争取
  • 类准备,这个阶段准备代表的每个类中定义的字段、方法和实现接口所必需的数据结构
  • 解析,在这个阶段类装入器装入类所引用的其他类。可以用许多方式引用类,如超类、结构、字段、方法签名、方法中使用的本地变量

4. 初始化Class对象

类中包含的静态初始化器都被执行,在这一阶段末尾静态字段被初始化默认值。

四、常见加载类错误分析

在执行Java程序时经常会碰到ClassNotFoundExceptionNoClassDefFoundError两个异常,它们都与类加载有关,下面分析一下产生这些异常的原因:

1. ClassNotFoundException

这个异常通常发生在显示加载类的时候,例如,用如下方式调用加载一个类时就报了这个错:

public class Main {public static void main(String[] args) throws ClassNotFoundException {Class.forName("Jack");}
}

在这里插入图片描述
出现这个错误的原因是,JVM要加载指定的文件的字节码到内存时,并没有找到这个文件对应的字节码,也就是这个文件并不存在(在当前classpath目录下)。

获取classpath路径的方法:this.getClass().getClassLoader().getResource("").toString()

2. NoClassDefFoundError

这个异常在第一次使用命令执行Java类时很可能会碰到,出现这种异常的可能原因是使用new关键字、属性引用某个类、继承了某个接口或类,以及方法的某个参数引用类某个类,这时会触发JVM的隐时加载这些类时发现这些类不存在。解决这个错误的方法就是确保每个类的引用的类都在当前的classpath下面。

3. UnsatisfiledLinkError

这个异常通常是JVM启动时,如果一不小心将JVM中的某个lib删除了,就可能会报这个错误。

public class Main {public native void nativeMethod();static {System.loadLibrary("Nolib");}public static void main(String[] args) throws ClassNotFoundException {new Main().nativeMethod();}
}

在这里插入图片描述
上面就是在解析native标识的方法时JVM找不到对应的本机库文件出现。

4. ClassCastException

这个错误比较常见,通常在程序中出现强制类型转换时出现这个错误。JVM在做类型转换时会按照如下规则进行检查:

  • 对于普通对象,对象必须是目标类的实例或目标类的子类实例。如果目标类是一个接口,那么会把它当作实现该接口的一个字类。
  • 对于数组类型,目标类必须是数组类型或java.lang.Objecgt、java.lang.CLoneable、java.io.Serializable

如果不满足上面规则,JVM就会报这个错误。要避免这个错误有两种方式:

  • 在容器类型中显示地指明这个容器所包含的对象类型
  • 先通过instanceof检查是不是目标类型,然后再进行强制类型转换

5. ExceptionInInitializerError

这个错误JVM规范中是这样定义的:

  • 如果Java虚拟机试图创建类ExceptionInInitializerError的新实例,但是因为出现Out-Of-Memory-Error而无法创建新实例,那么就会抛出OutOfMemoryError对象作为代替
  • 如果初始化器抛出一些Exception,而且Exception类不是Error或者它的某个子类,那么就会创建ExceptionInInitializerError类的一个新实例,并用Exception作为参数,用这个实例代替Exception

认值。

五、自定义ClassLoader的优势

通过前面的分析,ClassLoader能够完成的事情无非以下几种情况:

  • 在自定义路径下查找自定义的class文件,也许我们需要的class文件并不总是在已经设置好的ClassPath下面,那么我们必须想办法找到这个类,在这种情况下我们需要自己实现一个ClassLoader
  • 对我们自己的要加载的类做特殊处理,如保证通过网络传输的类的安全性,可以将类经过加密后再传输,在加载到JVM之前需要对类的字节码再解密,这个过程就可以在自定义的ClassLoader中实现
  • 可以定义类的实效机制,如果我们可以检查已经加载的class文件是否修改,如果修改类可以重新加载这个类,从而实现类的热部署。

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

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

相关文章

jmeter,动态参数之随机数、随机日期

通过函数助手&#xff0c;执行以下配置&#xff1a; 执行后的结果树&#xff1a; 数据库中也成功添加了数据&#xff0c;对应字段是随机值&#xff1a;

『番外篇三』Swift “乱弹”之带索引遍历异步序列(AsyncSequence)

概览 在 Swift 开发中,我们往往在遍历集合元素的同时希望获得元素对应的索引。在本课中,我们将向小伙伴们展示除 enumerated() 方法之外的几种实现思路。在玩转普通集合之后,我们将用“魔法棒”进一步搞定异步序列带索引遍历的实现。 在本篇博主中,您将学到以下内容: 概…

国标级联/流媒体音视频平台EasyCVR设备录像下载异常该如何解决?

视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#xff0c;也能支…

7. 异常、断言及日志

1.异常 1).什么是异常 异常&#xff0c;就是不正常的意思。指的是程序在执行过程中&#xff0c;出现的非正常的情况&#xff0c;最终会导致JVM的非正常停止。 在Java等面向对象的编程语言中&#xff0c;异常本身是一个类&#xff0c;产生异常就是创建异常对象并抛出了一个异常…

2023年度总结:3个常用代理IP服务商实测对比

转眼到了2023年末&#xff0c;即将踏入2024新年&#xff0c;业务也即将走过年底冲刺暂告一段落。在这一年&#xff0c;为了保证公司项目的稳定运行&#xff0c;我们花费了大量的精力与费用测试在全球范围内的多家代理IP服务商&#xff0c;综合实测各家产品与服务后&#xff0c;…

mybatis-plus雪花算法自动生成ID到前端后精度丢失问题

问题发生 前端接收到后端的数据出现异常&#xff0c;异常如下&#xff1a; 如图这是后端正常返回的数据&#xff0c; 但是点击预览时发现这个id的数据被改变了 这就导致了我通过id去修改相关数据时无法成功 问题原因 id的长度过长&#xff08;19位&#xff09;&#xff0c;前…

前端(一):HTML+CSS

参考课程&#xff1a;23最新版web前端开发_哔哩哔哩_bilibili 文档&#xff1a;GitHub - codeNiuMa/HTML-md-file: 学习HTML课程时的资料 目录 1 HTML 1.1 骨架 DOCTYPE html标签 head标签 body标签 title标签 meta标签 1.2 标签标题h1 1.3 段落p 1.4 水平线 1.5 图片img 1.6 …

MacOS下载配置OpenCV

主要参考的是OpenCV官方的这篇文章&#xff1a;OpenCV: Installation in MacOS 安装OpenCV需要下载一些安装包&#xff1a;CMake3.9、Git、Python这些我之前已经下载好&#xff0c;这里就不过多阐述了&#xff0c;自行百度安装即可 1.从Git库获取OpenCV&#xff1a; git clon…

[NAND Flash] 3.3 Flash闪存工艺知识深度解析

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 全文 3800 字。 1. Wafer 1.1 什么是 Wafer Wafer即晶圆&#xff0c;是半导体组件“晶片”或“芯片”的基材&#xff0c;从沙子里面…

Java医院信息化建设云HIS系统源码

云HIS提供标准化、信息化、可共享的医疗信息管理系统&#xff0c;实现医患事务管理和临床诊疗管理等标准医疗管理信息系统的功能。优化就医、管理流程&#xff0c;提升患者满意度、基层首诊率&#xff0c;通过信息共享、辅助诊疗等手段&#xff0c;提高基层医生的服务能力构建和…

喜讯连连捷报传,文章累累创佳绩,平均IF 18分!!!

爱基百客专注于提供领先的表观组学服务&#xff0c;目前已在表观组学、单细胞时空组学和高通量测序等方向积累了大量的项目经验。近期&#xff08;11月23日-12月4日&#xff09;&#xff0c;高分项目文章频出&#xff0c;涉及物种有梨、腰果、蘑菇、青枯雷尔氏菌和心肌细胞&…

系统的安全性设计

要设计一个安全的系统&#xff0c;除了要了解一些前面讲到的常用的保护手段和技术措施外&#xff0c;还要对系统中可能出现的安全问题或存在的安全隐患有充分的认识&#xff0c;这样才能对系统的安全作有针对性的设计和强化&#xff0c;即“知己知彼&#xff0c;百战百胜”。 下…

【Oracle】创建表

目录 方法一&#xff1a;CREATE TABLE 语法 创建表示例1&#xff1a;创建stuinfo(学生信息表) 创建表示例2&#xff1a;添加stuinfo(学生信息表)约束 方法二&#xff1a;CREATE TABLE AS 语法 创建表示例3&#xff1a; 创建表示例4&#xff1a;实现对select查询的结果进行…

什么是数据可视化?数据可视化的优势、方法及示例

前言 在当今的数字时代&#xff0c;数据是企业和组织的命脉&#xff0c;生成的数据量呈指数级增长。这种被称为大数据的海量数据在洞察力和决策方面具有巨大的潜力。然而&#xff0c;如果没有一种有效的方法来分析和理解这些数据&#xff0c;它就会变得毫无意义和难以管理。这就…

四十六、Redis哨兵

目录 一、哨兵的作用及原理 1、哨兵的结构和作用如下&#xff1a; 2、服务状态监控 3、选举新的master 4、小结 二、RedisTemplate的哨兵模式 一、哨兵的作用及原理 Redis提供了哨兵&#xff08;Sentinel&#xff09;机制来实现主从集群的自动故障恢复。 1、哨兵的结构和作…

【电路笔记】-电容器

电容器 文章目录 电容器1、概述2、电容器的电容单位3、电容4、电容器示例15、电介质6、额定电压7、总结 电容器是简单的无源器件&#xff0c;当连接到电压源时&#xff0c;可以在极板上存储电荷。 1、概述 在本电容器简介文章中&#xff0c;我们将看到电容器是无源电子元件&am…

【Spring】Spring AOP

Spring AOP AOP概述什么是AOP Spring AOP快速入门1.引入AOP依赖2. 编写AOP程序 Spring AOP 详解Spring AOP 核心概念切点(Pointcut)连接点(Join Point)通知(Advice)切面(Aspect) 通知类型PointCut切面优先级Order切点表达式execution表达式annotation自定义注解切面类 AOP原理代…

Leetcode—896.单调数列【简单】

2023每日刷题&#xff08;五十九&#xff09; Leetcode—896.单调数列 实现代码 class Solution { public:bool isMonotonic(vector<int>& nums) {int up 0;int down 0;if(nums.size() 1) {return true;}for(int i 0; i < nums.size() - 1; i) {if(nums[i] …

前端自定义icon的方法(Vue项目)

第一步&#xff1a;进入在线的编辑器进行设计 好用&#xff1a;百度字体编辑器 比如先导入有个ttf文件 添加新字体 双击每个模块进入编辑区域 更改相应的信息&#xff0c;比如name 编辑完了进行导出文件(各种格式就行了)就行了 第二步&#xff1a;在项目中asset文件储存这些文…

【PS】修改 图片 文字

修改文字 1&#xff1a;框选要修改的文字 选择-色彩范围 调整色彩容差能看见字体的时候就OK&#xff08;记住用吸管吸取文字颜色&#xff09; 2&#xff1a;选择-修改-扩展-像素2 3&#xff1a;编辑-内容识别填充 现在文字去除了。 用污点画笔修复工具&#xff0c;对缺陷进行…