JVM系列(二) -类的加载过程介绍

一、背景介绍

我们知道 Java 是先通过编译器将.java类文件转成.class字节码文件,然后再通过虚拟机将.class字节码文件加载到内存中来实现应用程序的运行。

那么虚拟机是什么时候加载class文件?如何加载class文件?class文件进入到虚拟机后发生了哪些变化?

今天我们就一起来了解一下,虚拟机是如何加载类文件的。

二、类加载的时机

经常有面试官问,“类什么时候加载”和“类什么时候初始化”,从内容上来说,似乎都在问同一个问题:class文件是什么时候被虚拟机加载到内存中,并进入可以使用的状态?

从虚拟机角度来说,加载初始化是类的加载过程中的两个阶段。

对于“什么时候加载”,Java 虚拟机规范中并没有约束,每个虚拟机实例都可以按自身需要来自由实现。但基本上都遵循类在进行初始化之前,需要先进行加载class文件。

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

  • 使用new实例化对象时,读取或者设置一个类的静态字段或方法时
  • 反射调用时,例如Class.forName("com.xxx.Test")
  • 初始化一个类的子类,会首先初始化子类的父类
  • Java 虚拟机启动时标明的启动类,比如main方法所在的类
  • JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会在它之前进行初始化

类在初始化开始之前,需要先经历加载、验证、准备、解析这四个阶段的操作。

下面我们一起来看看类的加载过程。

三、类的加载过程

当一个类需要被加载到虚拟机中执行时,虚拟机会通过类加载器,将其.class文件中的字节码信息在内存中转化成一个具体的java.lang.Class对象,以便被调用执行。

类从被加载到虚拟机内存中开始,到卸载出内存,整个生命周期包括七个阶段:加载、验证、准备、解析、初始化、使用和卸载,可以用如下图来简要概括。

其中类加载的过程,可以用三个步骤(五个阶段)来简要描述:加载 -> 连接(验证、准备、解析)-> 初始化。(验证、准备、解析这3个阶段统称为连接

其次加载、验证、准备和初始化这四个阶段发生的顺序是确定的,必须按照这种顺序按部就班的开始,而解析阶段则不一定。在某些情况下解析阶段可以在初始化阶段之后开始,这是为了支持 Java 语言的运行时绑定,也称为动态绑定晚期绑定

同时,这五个阶段并不是严格意义上的按顺序完成,在类加载的过程中,这些阶段会互相混合,可能有些阶段完成了,有些阶段没有完成,会交叉运行,最终完成类的加载和初始化。

接下来依此分解一下加载、验证、准备、解析、初始化这五个步骤,这五个步骤组成了一个完整的类加载过程。使用没什么好说的,卸载通常属于 GC 的工作,当一个类没有被任何地方引用并且类加载器已被 GC 回收,GC 会将当前类进行卸载,在后续的文章我们会介绍 GC 的工作机制。

3.1、加载

加载是类加载的过程的第一个阶段,这个阶段的主要工作是查找并加载类的二进制数据,在虚拟机中,类的加载有两种触发方式:

  • 预先加载:指的是虚拟机启动时加载,例如JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面包含了程序运行时常用的文件内容,例如java.lang.*java.util.*java.io.*等等,因此会随着虚拟机启动时一起加载到内存中。要证明这一点很简单,自己可以写一个空的main函数,设置虚拟机参数为-XX:+TraceClassLoading,运行程序就可以获取类加载的全部信息
  • 运行时加载:虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有,就会按照类的全限定名来加载这个类;如果有,就不会加载。

无论是哪种触发方式,虚拟机在加载.class文件时,都会做以下三件事情:

  • 1.通过类的全限定名定位.class文件,并获取其二进制字节流
  • 2.将类信息、静态变量、字节码、常量这些.class文件中的内容放入运行时数据区的方法区
  • 3.在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口,一般这个java.lang.Class对象会存在 Java 堆中

虚拟机规范对这三点的要求并不具体,因此具体虚拟机实现的灵活度都很大。比如第一条,没有指明二进制字节流要从哪里来,单单就这一条,就能变出许多花样来,比如下面几种加载方式:

  • 从 zip、jar、ear、war 等归档文件中加载.class文件
  • 通过网络下载并加载.class文件,典型应用就是 Applet
  • Java源文件动态编译为.class文件,典型应用就是动态代理技术
  • 从数据库中提取.class文件并进行加载

总的来说,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)对于开发者来说是可控性最强的一个阶段。因为开发者既可以使用系统提供的类加载器来完成加载,也可以自定义类加载器来完成加载。

3.2、验证

验证是连接阶段的第一步,这一阶段的目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

Java 语言本身是比较安全的语言,但是正如上面说到的.class文件未必是从 Java 源码编译而来,可以使用任何途径来生成并加载。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。

验证阶段大致会完成 4 项检验工作:

  • 文件格式验证:验证字节流是否符合Class文件格式的规范,例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型等
  • 元数据验证:对字节码描述的元数据信息进行语义分析,要符合 Java 语言规范,例如:是否继承了不允许被继承的类(例如 final 修饰过的)、类中的字段、方法是否和父类产生矛盾等等
  • 字节码验证:对类的方法体进行校验分析,确保这些方法在运行时是合法的、符合逻辑的
  • 符号引用验证:确保解析动作能正确执行,例如:确保符号引用的全限定名能找到对应的类,符号引用中的类、字段、方法允许被当前类所访问等等

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

3.3、准备

准备是连接阶段的第二步,这个阶段的主要工作是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配

不过这个阶段,有几个知识点需要注意一下:

  • 1.这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在 Java 堆中
  • 2.这个阶段会设置变量的初始值,值为数据类型默认的零值(如 0、0L、null、false 等),不是在代码中被显式地赋予的值;但是当字段被final修饰时,这个初始值就是代码中显式地赋予的值
  • 3.在 JDK1.8 取消永久代后,方法区变成了一个逻辑上的区域,这些类变量的内存实际上是分配在 Java 堆中的,跟 JDK1.7 及以前的版本稍有不同

关于第二个知识点,我们举个简单的例子进行讲解,比如public static int value = 123value在准备阶段过后是0而不是123

因为这时候尚未开始执行任何 Java 方法,把value赋值为123public static指令是在程序编译后存放于类构造器<clinit>()方法之中的,因此把value赋值为123的动作将在初始化阶段才会执行。

假如被final修饰,比如public static final int value = 123就不一样了,编译时Javac将会为value生成ConstantValue属性,在准备阶段,虚拟机就会给value赋值为123,因为这个变量无法被修改,会存入类的常量池中。

各个数据类型的零值如下图:

数据类型零值
byte0
short0
int0
long0L
float0.0f
double0.0d
booleanfalse
char\u0000
referencenull
3.4、解析

解析是连接阶段的第三步,这个阶段的主要工作是虚拟机会把这个.class文件中常量池内的符号引用转换为直接引用

主要解析的是类或接口、字段、方法等符号引用,我们可以把解析阶段中符号引用转换为直接引用的过程,理解为当前加载的这个类和它所引用的类,正式进行“连接“的过程。

我们先来了解一下符号引用直接引用有什么区别:

  • 符号引用:这个其实是属于编译原理方面的概念,Java 代码在编译期间,是不知道最终引用的类型,具体指向内存中哪个位置的,这时候会使用一个符号引用来表示具体引用的目标是"谁",符号引用和虚拟机的内存布局是没有关系的
  • 直接引用:指的是可以直接或间接指向目标内存位置的指针或句柄,直接引用和虚拟机实现的内存布局是有关系的

符号引用转换为直接引用,可以理解成将某个符号与虚拟机中的内存位置建立连接,通过指针或句柄来直接访问目标。

与此同时,同一个符号引用在不同的虚拟机实现上翻译出来的直接引用一般不会相同。

3.5、初始化

初始化是类加载的过程的最后一步,这个阶段的主要工作是执行类构造器 <clinit>()方法的过程

简单的说,初始化阶段做的事就是给static变量赋予用户指定的值,同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

初始化阶段,虚拟机大致依此会进行如下几个步骤的操作:

  • 1.检查这个类是否被加载和连接,如果没有,则程序先加载并连接该类
  • 2.检查该类的直接父类有没有被初始化,如果没有,则先初始化其直接父类
  • 3.类中如果有多个初始化语句,比如多个static代码块,则依次执行这些初始化语句

有个地方需要注意的是:虚拟机会保证类的初始化在多线程环境中被正确地加锁、同步执行,所以无需担心是否会出现变量初始化时线程不安全的问题

如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都会阻塞等待,直到<clinit>()方法执行完毕。同时,同一个类加载器下,一个类只会初始化一次,如果检查到当前类没有初始化,执行初始化;反之,不会执行初始化。

与此同时,只有当对类的主动使用的时候才会触发类的初始化,触发时机主要有以下几种场景:

  • 1.创建类的实例对象,比如new一个对象操作
  • 2.访问某个类或接口的静态变量,或者对该静态变量赋值
  • 3.调用类的静态方法
  • 4.反射操作,比如Class.forName("xxx")
  • 5.初始化某个类的子类,则其父类也会被初始化,并且父类具有优先被初始化的优势
  • 6.Java 虚拟机启动时被标明为启动类的类,比如SpringBootApplication入口类

最后,<clinit>()方法和<init>()方法是不同的,一个是类构造器初始化,一个是实例构造器初始化,千万别搞混淆了啊。

四、小结

当一个符合 Java 虚拟机规范的.class字节码文件,经历加载、验证、准备、解析、初始化这些 5 个阶段相互协作执行完成之后,虚拟机会将此文件的二进制数据导入运行时数据区的方法区内,然后在堆内存中,创建一个java.lang.Class类的对象,这个对象描述了这个类所有的信息,同时提供了这个类在方法区的访问入口。

可以用如下图来简要描述。

与此同时,在方法区中,使用同一加载器的情况下,每个类只会有一份Class字节流信息;在堆内存中,使用同一加载器的情况下,每个类也只会有一份java.lang.Class类的对象。

写到最后

很早之前和小伙伴们分享过 JVM 相关的技术知识,再次感谢大家的支持和反馈。

经过几个月的努力,对 JVM 技术知识进行了重新整理,最后再次献上 JVM系列文章合集索引,感兴趣的小伙伴可以点击查看。

  • JVM系列(一) -什么是虚拟机
  • JVM系列(二) -类的加载过程
  • JVM系列(三) -内存布局详解
  • JVM系列(四) -对象的创建过程
  • JVM系列(五) -对象的内存分配流程
  • JVM系列(六) -运行期优化技术
  • JVM系列(七) -垃圾收集算法
  • JVM系列(八) -垃圾收集器
  • JVM系列(九) -GC日志分析
  • JVM系列(十) -常用调优命令汇总
  • JVM系列(十一) -常用调优工具介绍
  • JVM系列(十二) -常用调优参数总结

最后。如果感觉文章内容不错,帮忙动动小指头点个赞,点赞对我真的非常重要!加个关注我会非常感激!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python酷库之旅-第三方库Pandas(142)

目录 一、用法精讲 641、pandas.Timestamp.hour属性 641-1、语法 641-2、参数 641-3、功能 641-4、返回值 641-5、说明 641-6、用法 641-6-1、数据准备 641-6-2、代码示例 641-6-3、结果输出 642、pandas.Timestamp.is_leap_year属性 642-1、语法 642-2、参数 6…

【MySQL 08】复合查询

目录 1.准备工作 2.多表查询 笛卡尔积 多表查询案例 3. 自连接 4.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 1.union 2.union all 1.准备工作 如下三个表&#xff0c;将作为示例&#xff0c;理解复合查询 EMP员工表…

在IDEA里用XDebug调试PHP,断点....

做程序开发,调试必不可少,这里最近用到了PHP,顺便写个关于PHP的调试安装使用: 1、首先是PHP先安装xdebug扩展(还有zend的),这个我的工具是IDEA,所以安装方法也相对简单,如果你是用VSCode等应该也是一样,如下图,找到这个PHP->DEBUG 2、直接点上面的Install XDebug 就可以帮你…

C(十五)函数综合(一)--- 开公司吗?

在这篇文章中&#xff0c;杰哥将带大家 “开公司”。 主干内容部分&#xff08;你将收获&#xff09;&#xff1a;&#x1f449; 为什么要有函数&#xff1f;函数有哪些&#xff1f;怎么自定义函数以及获得函数的使用权&#xff1f;怎么对函数进行传参&#xff1f;函数中变量的…

springboot kafka多数据源,通过配置动态加载发送者和消费者

前言 最近做项目&#xff0c;需要支持kafka多数据源&#xff0c;实际上我们也可以通过代码固定写死多套kafka集群逻辑&#xff0c;但是如果需要不修改代码扩展呢&#xff0c;因为kafka本身不处理额外逻辑&#xff0c;只是起到削峰&#xff0c;和数据的传递&#xff0c;那么就需…

FastAPI框架使用枚举来型来限定参数、FastApi框架隐藏没多大意义的Schemes模型部分内容以及常见的WSGI服务器Gunicorn、uWSGI了解

一、FastAPI框架使用枚举来型来限定参数 FastAPI框架验证时&#xff0c;有时需要通过枚举的方式来限定参数只能为某几个值中的一个&#xff0c;这时就可以使用FastAPI框架的枚举类型Enum了。publish:December 23, 2020 -Wednesday 代码如下&#xff1a; #引入Enum模块 from fa…

Python常用的函数大全!

对Python的内置函数进行了非常详细且有条理的分组和描述。 第一组 print()&#xff1a;用于输出信息到控制台。input()&#xff1a;用于从用户那里接收输入。len()&#xff1a;返回对象&#xff08;如字符串、列表、元组等&#xff09;的长度。类型转换函数&#xff08;int()…

YOLOv11改进策略【损失函数篇】| 利用MPDIoU,加强边界框回归的准确性

一、背景 目标检测和实例分割中的关键问题&#xff1a; 现有的大多数边界框回归损失函数在不同的预测结果下可能具有相同的值&#xff0c;这降低了边界框回归的收敛速度和准确性。 现有损失函数的不足&#xff1a; 现有的基于 ℓ n \ell_n ℓn​范数的损失函数简单但对各种尺度…

vSAN06:ESA与OSA对比、ESA安装、新架构、工作方式、自动策略管理、原生快照、数据压缩、故障处理

目录 vSAN ESAvSAN ESA 安装ESA新架构ESA工作方式ESA自动策略管理自适应RAID5策略 原生快照支持数据压缩的改进ESA故障处理 vSAN ESA vSAN ESA 安装 流程和OSA完全一致&#xff0c;但要注意要勾选启用vSAN ESA ESA和OSA的底层架构不一样&#xff0c;但是UI上是一致的。 生产环…

使用Python编写你的第一个算法交易程序

背景 Background ​ 最近想学习一下量化金融&#xff0c;总算在盈透投资者教育&#xff08;IBKRCampus&#xff09;板块找到一篇比较好的算法交易入门教程。我在记录实践过程后&#xff0c;翻译成中文写成此csdn博客&#xff0c;分享给大家。 ​ 如果你的英语好可以直接看原文…

2024百度云智大会|百度大模型内容安全合规探索与实践

9月25日&#xff0c;2024百度云智大会在北京举办。会上&#xff0c;百度智能云分别针对算力、模型、AI 应用&#xff0c;全面升级百舸 AI 异构计算平台 4.0、千帆大模型平台 3.0 两大 AI 基础设施&#xff0c;并升级代码助手、智能客服、数字人三大 AI 原生应用产品。 在大模型…

[uni-app]小兔鲜-08云开发

uniCloud可以通过JS开发服务端,包含云数据库, 云函数, 云存储等功能, uniCloud可结合 uni-ui 组件库使用 效果展示: <picker>城市选择组件不支持h5端和APP端, 所以我们使用 <uni-data-picker>组件进行兼容处理 <uni-data-picker>的数据使用云数据库的数据 云…

K8s中pod的管理和优化

一、k8s中的资源 1.1 资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象 资源&#xff0c;用户需要通过操作资源来管理kubernetes。kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务所谓的部署服务&#xff0c;其实就是在kubernetes集群中…

【D3.js in Action 3 精译_030】3.5 给 D3 条形图加注图表标签(下):Krisztina Szűcs 人物专访 + 3.6 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

Hive3.x版本调优总结

文章目录 第 1 章 Explain 查看执行计划&#xff08;重点&#xff09;1.1 创建测试用表1&#xff09;建大表、小表和 JOIN 后表的语句2&#xff09;分别向大表和小表中导入数据 1.2 基本语法1.3 案例实操 第 2 章 Hive 建表优化2.1 分区表2.1.1 分区表基本操作2.1.2 二级分区2.…

WMS系统拣货管理的优化与创新

一、WMS系统拣货管理的重要性 随着电子商务的快速发展&#xff0c;物流仓储行业面临着巨大的挑战。订单量的激增导致传统的手工拣货方式难以满足需求&#xff0c;而WMS系统的引入则解决了这一问题。通过WMS系统&#xff0c;仓库可以实现自动化、智能化的拣货管理&#xff0c;大…

小米路由器R3Gv2安装openwrt记录

前言 小米路由器R3Gv2的硬件配置与小米路由器4A千兆版一致&#xff0c;但bootloader有所不同&#xff0c;因此openwrt的固件不要互刷。另外&#xff0c;R3Gv2和R3G、4A百兆版是不同的设备&#xff0c;切勿混淆。 硬件信息 OpenWrt参数页-Xiaomi MiWiFi 3G v2 CPU&#xff1a…

小猿口算APP脚本(协议版)

小猿口算是一款专注于数学学习的教育应用,主要面向小学阶段的学生。它提供多种数学练习和测试,包括口算、速算、应用题等。通过智能化的题目生成和实时批改功能,帮助学生提高数学计算能力。此外,它还提供详细的学习报告和分析,帮助家长和教师了解学生的学习进度和薄弱环节…

[含文档+PPT+源码等]精品大数据项目-基于python爬虫实现的大数据岗位的挖掘与分析

大数据项目——基于Python爬虫实现的大数据岗位的挖掘与分析&#xff0c;其背景主要源于以下几个方面&#xff1a; 一、大数据时代的来临 随着互联网、物联网、云计算等技术的快速发展&#xff0c;数据呈现出爆炸式增长。根据国际数据公司&#xff08;IDC&#xff09;的预测&…

新电脑 Windows 系统初始配置

文章目录 前言1 前置配置2 安装软件2.1 通讯工具2.2 后端开发工具2.3 硬件开发工具2.4 前端开发工具2.4 其它工具 3 Windows 11 优化4 写在最后 前言 分区&#xff08;个人习惯&#xff09;&#xff1a;1TB SSD 分为 2 个分区&#xff0c;一个 256GB 分区为系统盘&#xff0c;剩…