深入分析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程序时经常会碰到ClassNotFoundException
和NoClassDefFoundError
两个异常,它们都与类加载有关,下面分析一下产生这些异常的原因:
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文件是否修改,如果修改类可以重新加载这个类,从而实现类的热部署。
相关文章:

深入分析ClassLocader工作机制
文章目录 一、ClassLoader简介1. 概念2. ClassLoader类结构分析 二、ClassLoader的双亲委派机制三、Class文件的加载流程1. 简介2. 加载字节码到内存3. 验证与解析4. 初始化Class对象 四、常见加载类错误分析1. ClassNotFoundException2. NoClassDefFoundError3. UnsatisfiledL…...

算法通关村第十二关—字符串转换(青铜)
一、转换成小写字母 LeetCode709.给你一个字符串s,将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。 示例1: 输入:s"Hello" 输出:"hello" 示例2: 输入:s&qu…...
C#基础与进阶扩展合集-基础篇(持续更新)
目录 本文分两篇,进阶篇点击:C#基础与进阶扩展合集-进阶篇 一、基础入门 Ⅰ 关键字 Ⅱ 特性 Ⅲ 常见异常 Ⅳ 基础扩展 1、哈希表 2、扩展方法 3、自定义集合与索引器 4、迭代器与分部类 5、yield return 6、注册表 7、不安全代码 8、方法…...
ReactJs笔记摘录
文章目录 前言目录结构组件动态组件高阶组件 Hook函数useStateuseEffectuseContextuseReduceruseCallbackuseMemo JSX语法根元素与斜杠使用变量推荐使用className替代class属性写法三元表达式 vs &&antd和tailwindcss 组件通信父传子:props和自定义函数事件…...

2023 re:Invent使用 PartyRock 和 Amazon Bedrock 安全高效构建 AI 应用程序
前言 本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 亚马逊云科技开发者社区, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道 “Your Data, Your AI, Your Future.(你的数据,你的AI&…...
Mac 打不开github解决方案
序言 github 时有打不开的情况,为此很是烦恼,这里分享一下如何解决这种问题,其实问题的本质是在访问github网页时无法通过github.com的二级域名进行动态域名解析。 解决方案 手动配置静态文件hosts,将该域名和IP的映射关系添加…...

十五 动手学深度学习v2计算机视觉 ——全连接神经网络FCN
文章目录 FCN FCN 全卷积网络先使用卷积神经网络抽取图像特征,然后通过卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的高和宽变换为输入图像的尺寸。 因此,模型输出与输入图像的高和宽相同,且最终输出通道包含了该空…...

elementUI中的 “this.$confirm“ 基本用法,“this.$confirm“ 调换 “确认“、“取消“ 按钮的位置
文章目录 前言具体操作总结 前言 elementUI中的 "this.$confirm" 基本用法,"this.$confirm" 调换 "确认"、"取消" 按钮的位置 具体操作 基本用法 <script> this.$confirm(这是数据(res.data࿰…...
K8S 常用命令
获取所有的pod资源: kubectl get pod 获取所有的命名空间: kubectl get namespace 获取所有的Deployment资源: kubectl get deployment 删除指定的deploy: kubectl delete deploy nginx 获取所有的服务: kubectl get serv…...
12.使用 Redis 优化登陆模块
目录 1. 使用 Redis 优化登陆模块 1.1 使用 Redis 存储验证码 1.2 使用 Redis 存储登录凭证 1.3 使用 Redis 缓存用户信息 1. 使用 Redis 优化登陆模块 使用 Redis 存储验证码:验证码需要频繁的访问与刷新,对性能要求较高;验证码不需要永…...

Nacos-NacosRule 负载均衡—设置集群使本地服务优先访问
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 NacosRule 权重计算方法 目录 一、介绍 二、示例(案例截图) 三、总结 一、介绍 NacosRule是AlibabaNacos自己实现的一个负载均衡策略&…...

软件设计师——信息安全(二)
📑前言 本文主要是【信息安全】——软件设计师——信息安全的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄…...

Unity中实现ShaderToy卡通火(原理实现篇)
文章目录 前言一、我们在片元着色器中,实现卡通火的大体框架1、使用 noise 和 _CUTOFF 判断作为显示火焰的区域2、_CUTOFF : 用于裁剪噪波范围的三角形3、noise getNoise(uv, t); : 噪波函数 二、顺着大体框架依次解析具体实现的功能1、 uv.x * 4.0; : …...

引迈信息-JNPF平台怎么样?值得入手吗?
目录 1.前言 2.引迈低代码怎么样? 3.平台亮点展示 4.引迈产品特点 5.引迈产品技术栈: 1.前言 低代码是近几年比较火的一种应用程序快速开发方式,它能帮助用户在开发软件的过程中大幅减少手工编码量,并通过可视化组件加速应用…...

大数据云计算——使用Prometheus-Operator进行K8s集群监控
大数据云计算——使用Prometheus-Operator进行K8s集群监控 一、 背景 在非operator配置的普罗中我们监控k8s集群都是通过配置configmap进行服务发现和指标拉取。切换到prometheus-operator难免会有些使用问题。不少用户已经习惯底层配置自动发现的方式。当过渡到servicemonit…...

[蓝桥杯刷题]合并区间、最长不连续子序列、最长不重复数组长度
前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录: 成功的关键在于对目标的持久追求。 ⭐个人主页:欧_aita ψ(._. )>⭐个人专栏: 数据结构与算法 数据库 文章目录 前言合并区间问题📕现实应用大致思路代码实现代码讲解 最长不连续子序列&a…...

Hazel引擎学习(十二)
我自己维护引擎的github地址在这里,里面加了不少注释,有需要的可以看看 参考视频链接在这里 这是这个系列的最后一篇文章,Cherno也基本停止了Games Engine视频的更新,感觉也差不多了,后续可以基于此项目开发自己想要…...

中文字符串逆序输出
今天碰到这个题,让我逆序输出中文字符串,可给我烦死了,之前没有遇到过,也是查了资料才知道,让我太汗颜了。 英文字符串逆序输出很容易,开辟一块空间用来存放逆序后的字符串,从后往前遍历原字符串…...

MySQL BinLog 数据还原恢复
博文目录 文章目录 查看状态查看 binlog 开关及存储路径查看 binlog 配置 如 存储格式 binlog_format查看当前还存在的日志查看当前正在使用的日志 切换日志确定日志确定日志文件日志格式改写日志简要说明确定日志位置以事件为单位查看日志分析日志 还原数据 查看状态 查看 b…...
理想汽车校招内推--大量hc等你来
投递链接: https://li.jobs.feishu.cn/s/i8BLJE1j 欢迎大家投递...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...