当前位置: 首页 > news >正文

Java类加载机制

简介

在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。

类加载机制指的是将这些.class文件中的二进制数据读入到内存中,并对数据进行校验,解析和初始化。最终,每一个类都会在方法区保存一份它的元数据,在堆中创建一个与之对应的Class对象。

类的生命周期,经历7个阶段,分别是加载、验证、准备、解析、初始化、使用、卸载。

除了使用卸载两个过程,前面的5个阶段 加载、验证、准备、解析、初始化 的执行过程就是类的加载过程。

类加载的时机

大多数人在问 “类什么时候加载” 和 “类什么时候初始化” ,从语境上来说,都是在问同一个问题,就是这个.class文件什么时候被读取到虚拟机的内存中,并且达到可用的状态。

但严格意义上来说,加载和初始化,是类生命周期的两个阶段。

对于什么时候加载,Java虚拟机规范中并没有约束,各个虚拟机都可以按自身需要来自由实现。但绝大多数情况下,都遵循“什么时候初始化”来进行加载。

什么时候初始化?Java虚拟机规范有明确规定,当符合以下条件时(包括但不限于),虚拟机内存中没有找到对应类型信息,则必须对类进行“初始化”操作:

  • 使用new实例化对象时、读取或者设置一个类的静态字段或方法时
  • 反射调用时,例如 Class.forName("com.xxx.MyTest")
  • 初始化一个类的子类,会首先初始化子类的父类
  • Java虚拟机启动时标明的启动类
  • JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会其之前进行初始化
初始化阶段开始之前,自然还是要先经历 加载、验证、准备 、解析的。

类的加载过程

从简介中,我们知道,类的加载过程分 5 个阶段,其中 验证、准备、解析 可以归纳为 “连接” 阶段。

需要注意的是,这5个阶段,并不是严格意义上的按顺序完成,在类加载的过程中,这些阶段会互相混合,交叉运行,最终完成类的加载和初始化。

例如在加载阶段,需要使用验证的能力去校验字节码正确性。在解析阶段,也要使用验证的能力去校验符号引用的正确性。或者加载阶段生成Class对象的时候,需要解析阶段符号引用转直接引用的能力等等......

接下来,我们详细分解一下,这5个阶段,都做了什么事情。

加载

加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名去找到其对应的.class文件
  2. 将这个.class文件内的二进制数据读取出来,转化成方法区的运行时数据结构
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
Java虚拟机并没有规定类的字节流必从.class文件中加载,在加载阶段,程序员可以通过自定义的类加载器,自行定义读取的地方,例如通过网络、数据库等。

验证

Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机自身的安全。

验证阶段会完成以下校验:

文件格式验证:验证字节流是否符合Class文件格式的规范。例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 ...... 等等

元数据验证:对字节码描述的元数据信息进行语义分析,要符合Java语言规范。例如:是否继承了不允许被继承的类(例如final修饰过的)、类中的字段、方法是否和父类产生矛盾 ...... 等等

字节码验证:对类的方法体进行校验分析,确保这些方法在运行时是合法的、符合逻辑的。

符号引用验证:发生在解析阶段,符号引用转为直接引用的时候,例如:确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问 ...... 等等

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

验证阶段不是必须的,虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段,用来缩短类加载的时间,可以根据自身需求,使用 -Xverify:none参数来关闭大部分的类验证措施。

 

验证

Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机自身的安全。

验证阶段会完成以下校验:

文件格式验证:验证字节流是否符合Class文件格式的规范。例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 ...... 等等

元数据验证:对字节码描述的元数据信息进行语义分析,要符合Java语言规范。例如:是否继承了不允许被继承的类(例如final修饰过的)、类中的字段、方法是否和父类产生矛盾 ...... 等等

字节码验证:对类的方法体进行校验分析,确保这些方法在运行时是合法的、符合逻辑的。

符号引用验证:发生在解析阶段,符号引用转为直接引用的时候,例如:确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问 ...... 等等

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

验证阶段不是必须的,虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段,用来缩短类加载的时间,可以根据自身需求,使用 -Xverify:none参数来关闭大部分的类验证措施。

准备

这个阶段,类的静态字段信息(即使用 static 修饰过的变量)会得到内存分配,并且设置为初始值。

对于该阶段有以下几个知识点需要注意:

1、内存分配仅包括 static 修饰过的变量,而不包括实例变量,实例变量得等到对象实例化时分配内存。

2、初始值指的是变量数据类型的默认值,而不是被在Java代码中被显式地赋予的值。但是,当字段信息被 final 修饰成常量(ConstantValue)时,这个初始值就是Java代码中显式地赋予的值。

例如:public static int value = 3
类变量 value 在准备阶段设置的初始值 是 0,不是 3。把value赋值为3的 putstatic 指令是在程序编译后,存放于类构造器 <clinit>() 方法中的,所以把 value 赋值为 3 的动作将在初始化阶段才会执行。
当使用 final 修饰后:public static  final int value = 3
类变量 value 在准备阶段设置的初始值 是 3,不是 0。

3、在JDK8取消永久代后,方法区变成了一个逻辑上的区域,这些类变量的内存实际上是分配在Java堆中的。

解析

这个阶段,虚拟机会把这个Class文件中,常量池内的符号引用转换为直接引用。主要解析的是 类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。我们可以把解析阶段中,符号引用转换为直接引用的过程,理解为当前加载的这个类,和它所引用的类,正式进行“连接“的过程

什么是符号引用?
Java代码在编译期间,是不知道最终引用的类型,具体指向内存中哪个位置的,这时候会用一个符号引用,来表示具体引用的目标是"谁"。Java虚拟机规范中明确定义了符号引用的形式,符合这个规范的前提下,符号引用可以是任意值,只要能通过这个值能定位到目标。

什么是直接引用?
直接引用就是可以直接或间接指向目标内存位置的指针或句柄。

引用的类型,还未加载初始化怎么办?
当出现这种情况,会触发这个引用对应类型的加载和初始化。

初始化

这是类加载的最后一个步骤啦,初始化的过程,就是执行类构造器 <clinit>()方法的过程。

当初始化完成之后,类中static修饰的变量会赋予程序员实际定义的“值”,同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法的作用是什么?

还记得么?在准备阶段,已经对类中static修饰的变量赋予了初始值。<clinit>() 方法的作用,就是给这些变量赋予程序员实际定义的“值”。同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法是什么?

<clinit>() 方法 和 <init> 方法是不同的,它们一个是“类构造器”,一个是实例构造器。
Java虚拟机会保证子类<clinit>() 方法在执行前,父类的 <clinit>() 已经执行完毕。而 <init> 方法则需要显性的调用父类的构造器。
<clinit>() 方法由编译器自动生成,但不是必须生成的,只有这个类存在static修饰的变量,或者类中存在静态代码块但时候,才会自动生成<clinit>()方法

加载过程总结

当一个符合Java虚拟机规范的字节流文件,经历 加载、验证、准备、解析、初始化这些阶段相互协作执行完成之后,加载阶段读取到的Class字节流信息,会按虚拟机规定的格式,在方法区保存一份,然后Java 堆中,会创建一个 java.lang.Class 类的对象,这个对象描述了这个类所有信息,也提供了这个类在方法区的访问入口。

方法区中,使用同一加载器的情况下,每个类只会有一份Class字节流信息
Java堆中,使用同一加载器的情况下,每个类只会有一份 java.lang.Class 类的对象

类加载器

还记得在加载阶段,通过类的全限定名,获取该类字节流数据的这个动作么,类加载器就是用来实现这个动作的。

当年为了满足浏览器上 Java Applet 的需求,Java的开发团队设计了类加载器,它独立于Java虚拟机外部,允许程序员按自身需要自行实现类加载器。这是一项非常优秀的创新,它让同一个类可以实现访问隔离、OSGi、程序热部署等等。发展至今,类加载器已经是Java技术体系的一块重要基石。

三层类加载器介绍

启动类加载器(Bootstrap Class Loader):负责加载<JAVA_HOME>\lib 目录,或者被 -Xbootclasspath 参数制定的路径,例如 jre/lib/rt.jar 里所有的class文件。由C++实现,不是ClassLoader子类。

拓展类加载器(Extension Class Loader):负责加载Java平台中扩展功能的一些jar包,包括<JAVA_HOME>\lib\ext 目录中 或 java.ext.dirs 指定目录下的jar包。由Java代码实现。

应用程序类加载器(Application Class Loader):我们自己开发的应用程序,就是由它进行加载的,负责加载ClassPath路径下所有jar包。

双亲委派模型

高端的食材往往只需要最简单的烹饪方式,而保证Java程序稳定运行的双亲委派模式,其实也非常简单:

双亲委派模式其实一句话就可以说清楚:任何一个类加载器在接到一个类的加载请求时,都会先让其父类进行加载,只有父类无法加载(或者没有父类)的情况下,才尝试自己加载。

 

ClassLoader 类中有示例,如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{// 首先要保证线程安全synchronized (getClassLoadingLock(name)) {// 先判断这个类是否被加载过Class<?> c = findLoadedClass(name);if (c == null) {try {// 有父类,优先交给父类尝试加载if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父类加载失败,这里捕获异常,但不需要做任何处理}if (c == null) {// 没有父类,或者父类无法加载,尝试自己加载c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}
}

 

双亲委派模型好处是什么?

在解答这个问题前,需要先了解一个知识点:不同的类加载器,加载同一个类,结果是虚拟机里会存在两份这个类的信息,所以当判断这两个类是否“相等”时,必定是不相等的。

使用双亲委派模式,可以保证,每一个类只会有一个类加载器。例如Java最基础的Object类,它存放在 rt.jar 之中,这是 Bootstrap 的职责范围,当向上委派到 Bootstrap 时就会被加载。

但如果没有使用双亲委派模式,可以任由自定义加载器进行加载的话,Java这些核心类的API就会被随意篡改。

相关文章:

Java类加载机制

简介 在Java的世界里&#xff0c;每一个类或者接口&#xff0c;在经历编译器后&#xff0c;都会生成一个个.class文件。 类加载机制指的是将这些.class文件中的二进制数据读入到内存中&#xff0c;并对数据进行校验&#xff0c;解析和初始化。最终&#xff0c;每一个类都会在…...

vue 自定义指令简单记录

自定义指令例子 // src/main.js import { createApp } from vue; import App from ./App.vue;const app = createApp(App);// 全局自定义指令 app.directive(color-directive, {mounted(el, binding) {// 当指令绑定到元素上时触发// el 是绑定的元素// binding 包含了指令的信…...

算法通关村-----快速排序的原理和实现

快速排序介绍 快速排序是一种经典高效的排序方法&#xff0c;是分治策略在排序上的具体体现。将一个大的待排序列分割成若干个小的有序序列&#xff0c;最终将各个小的有序序列合并成一个大的有序序列。 快速排序的实现原理 选择一个基准值&#xff0c;将小于基准值的元素放…...

百度抓取香港服务器抓取超时是什么情况?

​ 网络延迟导致抓取超时 网络延迟是指从发送请求到接收响应之间的时间延迟。如果网络延迟过高&#xff0c;服务器可能无法及时响应请求&#xff0c;导致超时。在香港服务器上抓取数据时&#xff0c;如果网络延迟过高&#xff0c;可能会出现抓取超时的情况。 服务器负载过高可能…...

Springboot上传文件

上传文件示例代码&#xff1a; ApiOperation("上传文件") PostMapping(value "/uploadFile", consumes MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResult<String> uploadFile(RequestPart("file") MultipartFile file) { //调用七…...

kafka教程

kafka教程 Kafka是一个分布式、分区的、多副本的、多订阅者&#xff0c;基于zookeeper协调的分布式日志系统&#xff0c;其主要特点为&#xff1a; 以时间复杂度为O(1)的方式提供消息持久化能力&#xff0c;即使对TB级以上数据也能保证常数时间的访问性能高吞吐率。即使在非常…...

JVM的故事—— 内存分配策略

内存分配策略 文章目录 内存分配策略一、对象优先在Eden分配二、大对象直接进入老年代三、长期存活的对象将进入老年代四、动态对象年龄判定五、空间分配担保 一、对象优先在Eden分配 堆内存有新生代和老年代&#xff0c;新生代中有一个Eden区和一个Survivor区(from space或者…...

21.CSS的动态圆形进度条

效果 源码 <!doctype html> <html><head><meta charset="utf-8"><title>Animated Circular Progress | CSS Only</title><link rel="stylesheet" href="style.css"></head><body><di…...

Linux_VMware_虚拟机磁盘扩容

来源文章 &#xff1a;VMware教学-虚拟机扩容篇_vmware虚拟机扩容_系统免驱动的博客-CSDN博客 由于项目逐步的完善&#xff0c;需要搭建的中间件&#xff0c;软件越来越多&#xff0c;导致以前虚拟机配置20G的内存不够用了&#xff0c;又不想重新创建新的虚拟机&#xff0c;退…...

中欧财富:分布式数据库的应用历程和 TiDB 7.1 新特性探索

原文来源&#xff1a; https://tidb.net/blog/ccbaeda2 作者&#xff1a;张政俊&#xff0c; 中欧财富数据库负责人 导读 中欧财富是中欧基金控股的销售子公司&#xff0c;旗下 APP 实现业内基金品种全覆盖&#xff0c;提供基金交易、大数据选基、智慧定投、理财师咨询等…...

树莓 LUMA-OLED.EXAMPLE使用

详细介绍在文件目录下的README.rst中 第一步 $ sudo usermod -a -G i2c,spi,gpio pi //好像没什么用 $ sudo apt install python3-dev python3-pip python3-numpy libfreetype6-dev libjpeg-dev build-essential //安装依赖包&#xff0c;树莓派中好像已经有了 $ sudo a…...

C#,《小白学程序》第十一课:双向链表(Linked-List)其二,链表的插入与删除的方法(函数)与代码

1 文本格式 /// <summary> /// 改进的车站信息类 class /// 增加了 链表 需要的两个属性 Last Next /// </summary> public class StationAdvanced { /// <summary> /// 编号 /// </summary> public int Id { get; set; } 0; ///…...

java IDEA文件路径分层级

如下图这样 在设置里找到Compact Middle Packages&#xff0c;去掉勾选就行了...

Spring AOP+Redis实现接口访问限制

目录 一、需求二、实现思路三、代码实现3.1 导入依赖3.2 配置redis3.3 自定义注解3.4 定义切面类3.5 自定义异常类3.6 全局异常处理器 一、需求 在我们程序中&#xff0c;有时候需要对一些接口做访问控制&#xff0c;使程序更稳定&#xff0c;最常用的一种是通过ip限制&#x…...

互联网后端技术大全!

互联网后端技术大全&#xff01; 一. 系统开发 高内聚/低耦合 高内聚 高内聚指一个软件模块是由相关性很强的代码组成&#xff0c;只负责一项任务&#xff0c;也就是常说的单一责任原则。模块的内聚反映模块内部联系的紧密程度。 低耦合 模块之间联系越紧密&#xff0c;其…...

Android SDK 上手指南||第九章 Manifest文件

第九章 Manifest文件 到目前为止&#xff0c;我们已经熟悉了Android项目中的各个组成部分&#xff0c;包括其资源。在今天的文章中&#xff0c;我们将以项目Manifest文件作为核心内容。 对于一个项目来说&#xff0c;Manifest既可以很简单、也可以很复杂&#xff0c;其具体情…...

CVE-2023-3450:锐捷 RG-BCR860 命令执行漏洞复现

锐捷 RG-BCR860 命令执行漏洞(CVE-2023-3450)复现 0x01 前言 本次测试仅供学习使用&#xff0c;如若非法他用&#xff0c;与本文作者无关&#xff0c;需自行负责&#xff01;&#xff01;&#xff01; 0x02 漏洞描述 Ruijie Networks RG-BCR860是中国锐捷网络&#xff08;R…...

【ES】elasticsearch8.3.3

这里仅实践操作并根据实际问题进行记录笔记。 运行 ES8 我们需要在自己的电脑上安装好 Docker Desktop。接着我们运行如下的命令&#xff1a;出现两个异常&#xff0c;一个是需要使用winpty因为我使用win的docker desktop&#xff0c;另外一个问题是docker启动elasticsearchE…...

2023年下半年广州/深圳软考(中/高级)认证报名,当然弘博创新

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…...

2017. 网格游戏;2397. 被列覆盖的最多行数;2202. K 次操作后最大化顶端元素

2017. 网格游戏 核心思想&#xff1a;前缀和枚举。读完题后可以发现&#xff0c;第一个机器人走的路线就像一条分割线&#xff0c;第二个机器人只能获得上面白色部分或者下面白色部分的最大值。这个最大值怎么求&#xff0c;我们可以通过前缀和来求&#xff0c;然后通过枚举转…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...