Java 入门指南:JVM(Java虚拟机)——类的生命周期与加载过程
文章目录
- 类的生命周期
- 类加载过程
- 1)载入(Loading)
- 2)验证(Verification)
- 文件格式验证
- 符号引用验证
- 3)准备(Preparation)
- 4)解析(Resolution)
- 符号引用
- 直接引用
- 5)初始化(Initialization)
JVM 运行 Java 代码的时候,需要将编译后的字节码文件加载到其内部的运行时数据区域中进行执行。这个过程涉及到了 Java 的类加载机制。
![![[Pasted image 20240915212223.png]]](https://i-blog.csdnimg.cn/direct/13d4bc14aa98449e8c4aebcd22bc0437.png)
类的生命周期
类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段:
- 加载(Loading):类加载的过程由类加载器(ClassLoader)完成,类加载器负责将字节码文件(
.class文件)加载到 JVM 中。类加载器的主要职责包括:
- 加载类文件:从指定位置读取字节码文件,并将其转换成字节数组。
- 创建 Class 对象:将字节数组转换成
Class对象,并将其存储在方法区中。
- 验证(Verification):验证阶段是为了确保类文件的数据符合 JVM 的规范,不会对 JVM 造成危害。验证主要分为四个阶段:
- 文件格式验证:确保字节流的格式符合 Class 文件格式规范。
- 元数据验证:确保类的元数据信息(如常量池中的常量)正确无误。
- 字节码验证:确保字节码指令符合 JVM 规范,不会导致非法操作。
- 符号引用验证:确保符号引用能正确解析到实际存在的类、接口、方法或字段。
-
准备(Preparation):准备阶段主要是为类变量分配内存空间,并设置类变量的初始值。注意,这里的类变量指的是被
static修饰的变量。实例变量则是在对象实例化时分配内存空间。 -
解析(Resolution):解析阶段是将符号引用转换为直接引用的过程。符号引用指的是类名、接口名、方法名等字符串形式的引用,而直接引用则指向目标对象在内存中的地址。
-
初始化(Initialization):初始化阶段是执行类构造器 (
<clinit>) 方法的过程。在这个阶段,类中的静态变量会被赋予初始值,并执行静态块中的代码。初始化阶段还包括类中非静态方法的调用和类的实例化。 -
使用(Using):类初始化完成后即可正常使用。此时可以创建类的实例,调用类的方法等。
-
卸载(Unloading):当一个类不再被引用,并且垃圾回收器确定没有对该类的引用后,类加载器会释放该类占用的资源,最终类会被卸载。卸载的过程是由 JVM 自动管理的,通常发生在内存压力较大时。
其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。这 7 个阶段的顺序如下图所示:
![![[一个类的完整生命周期.png]]](https://i-blog.csdnimg.cn/direct/184846f98ace43cd9b51323f94395cb9.png)
类加载过程
类的生命周期除去使用和卸载,就是 Java 的类加载过程。这 5 个阶段一般是顺序发生的,但在动态绑定的情况下,解析阶段发生在初始化阶段之后。系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
![![[Pasted image 20240915212736.png]]](https://i-blog.csdnimg.cn/direct/f8a4ec1acfa14605b9dd5cce25a9d59d.png)
1)载入(Loading)
类加载过程的第一步,主要完成下面 3 件事情:
- 通过全类名获取定义此类的二进制字节流。
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个代表该类的
Class对象,作为方法区这些数据的访问入口。
即将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。
虚拟机规范上面这 3 点并不具体,因此是非常灵活的。比如:“通过全类名获取定义此类的二进制字节流” 并没有指明具体从哪里获取( ZIP、 JAR、EAR、WAR、网络、动态代理技术运行时动态生成、其他文件生成比如 JSP…)、怎样获取。
加载这一步主要是通过 类加载器 完成的。类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载由 双亲委派模型 决定
每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。
一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 loadClass() 方法)。
加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
2)验证(Verification)
JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
验证阶段这一步在整个类加载过程中耗费的资源还是相对较多的,但很有必要,可以有效防止恶意代码的执行。任何时候,程序安全都是第一位。
验证阶段主要由四个检验阶段组成:
- 文件格式验证(Class 文件格式检查)
- 元数据验证(字节码语义检查)
- 字节码验证(程序语义检查)
- 符号引用验证(类的正确性检查)
![![[Pasted image 20240915213543.png]]](https://i-blog.csdnimg.cn/direct/f566a9d078e34c32b94f8ec7a2ecb087.png)
文件格式验证
文件格式验证这一阶段是基于该类的二进制字节流进行的,主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。除了这一阶段之外,其余三个验证阶段都是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。
方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
符号引用验证
符号引用验证发生在类加载过程中的解析阶段,具体来说是 JVM 将符号引用转化为直接引用的时候(解析阶段会介绍符号引用和直接引用)。
符号引用验证的主要目的是确保解析阶段能正常执行,如果无法通过符号引用验证,JVM 会抛出异常,比如:
-
java.lang.IllegalAccessError:当类试图访问或修改它没有权限访问的字段,或调用它没有权限访问的方法时,抛出该异常。 -
java.lang.NoSuchFieldError:当类试图访问或修改一个指定的对象字段,而该对象不再包含该字段时,抛出该异常。 -
java.lang.NoSuchMethodError:当类试图访问一个指定的方法,而该方法不存在时,抛出该异常。 -
…
3)准备(Preparation)
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
-
这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被
static关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 -
从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要注意的是:JDK 7 之前,
HotSpot使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着Class对象一起存放在 Java 堆中。
JVM 会在该阶段对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化,对应数据类型的默认初始值,"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等)。但若给变量加上了 final 关键字,在准备阶段变量的值就被赋值为指定的值。
基本数据类型的零值:
| 数据类型 | 零 值 |
|---|---|
| int | 0 |
| long | 0L |
| short | (short) 0 |
| char | ‘\u0000’ |
| byte | (byte) 0 |
| boolean | false |
| float | 0.0f |
| double | 0.0d |
| reference | null |
也就是说,假如有这样一段代码:
public String a = "Java";
public static String b = "Java";
public static final String c = "JVM";
a 不会被分配内存,而 b 会;但 b 的初始值不是“Java”而是 null。
由于 static final 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 c 在准备阶段的值为“JVM”而不是 null。
4)解析(Resolution)
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 解析动作主要针对以下 7 类符号引用进行:
- 类或接口
- 字段
- 类方法
- 接口方法
- 方法类型
- 方法句柄
- 调用限定符
符号引用
符号引用(Symbol References):以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。
各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在 Class 文件格式中。符号引用在编译时生成,存储在编译后的字节码文件的常量池中。
例如,在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 com.Order 类引用了 com.Item 类,编译时 Order 类并不知道 Item 类的实际内存地址,因此只能使用符号 com.Item。
直接引用
直接引用(Direct References):可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。通过对符号引用进行解析,找到引用的实际内存地址。
直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。由于直接指向了内存地址或者偏移量,所以通过直接引用访问对象的效率较高。
在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
5)初始化(Initialization)
初始化阶段是执行初始化方法 <clinit> () 方法的过程,该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。
例如:
String temp = new String("JVM");
上面这段代码使用了 new 关键字来实例化一个字符串对象,那么这时候,就会调用 String 类的构造方法对 temp 进行实例化。
public String(String original) {this.value = original.value;this.hash = original.hash;
}
对于 <clinit> () 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 <clinit> () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞,并且这种阻塞很难被发现。
对于初始化阶段,虚拟机严格规范了有且只有 6 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):
-
当遇到
new、getstatic、putstatic或invokestatic这 4 条字节码指令时,比如new一个类,读取一个静态字段(未被final修饰)、或调用一个类的静态方法时。- 当 jvm 执行
new指令时会初始化类。即当程序创建一个类的实例对象。 - 当 jvm 执行
getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。 - 当 jvm 执行
putstatic指令时会初始化类。即程序给类的静态变量赋值。 - 当 jvm 执行
invokestatic指令时会初始化类。即程序调用类的静态方法。
- 当 jvm 执行
-
使用
java.lang.reflect包的方法对类进行反射调用时如Class.forName("..."),newInstance()等等。如果类没初始化,需要触发其初始化。 -
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
-
当虚拟机启动时,用户需要定义一个要执行的主类 (包含
main方法的那个类),虚拟机会先初始化这个类。 -
MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这 2 个调用,就必须先使用findStaticVarHandle来初始化要调用的类。 -
当一个接口中定义了 JDK8 新加入的默认方法(被
default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
相关文章:
Java 入门指南:JVM(Java虚拟机)——类的生命周期与加载过程
文章目录 类的生命周期类加载过程1)载入(Loading)2)验证(Verification)文件格式验证符号引用验证 3)准备(Preparation)4)解析(Resolution…...
Unity射击游戏开发教程:(36)敌人关卡生成器的设计和开发
丰富多样地游戏关卡生成器能自动生成不同的关卡地图和游戏内容,以增加游戏的可玩性和挑战性。关卡生成可以基于随机算法或者预设的规则生成不同的地图布局、敌人位置、道具位置等。 定义关卡生成器WaveSpawner 如何设置通用的 Wave Spawner?我将此 Wave Spawner 脚本附加到…...
AI对汽车行业的冲击和比亚迪新能源汽车市场占比
人工智能(AI)对汽车行业的冲击正在迅速改变该行业的面貌,从智能驾驶到生产自动化,再到个性化的消费者体验,AI带来的技术革新在各个层面影响着汽车产业。与此同时,新能源汽车市场,特别是以比亚迪…...
2024年中国电子学会青少年软件编程(Python)等级考试(一级)核心考点速查卡
考前练习: 2024年06月中国电子学会青少年软件编程(Python)等级考试试卷(一级)答案 解析-CSDN博客 2024年03月中国电子学会青少年软件编程(Python)等级考试试卷(一级)答…...
游戏开发引擎__游戏场景(灯光,摄像机)
1.灯光 重要参数介绍 类型: 控制灯光的类型,有“定向”“点”“区域”和“聚光”4种模式。颜色: 控制灯光的颜色。模式: 控制灯光的光照模式,有“实时”“混合”和“烘焙”3种模式。强度: 控制灯光的明亮程度。间接乘数: 改变间接光的强度。阴影类型: …...
2024 Snap 新款ar眼镜介绍
2024 snap 新款ar眼镜介绍 2024 Snap 新款ar眼镜介绍 咨询合作 DataBall 项目,欢迎加以下微信。 助力快速掌握数据集的信息和使用方式。...
uni-app生命周期
目录 一、页面生命周期 1、onLoad 【常用】 2、onShow【常用】 3、onReady【常用】 4、onHide【常用】 5、onPullDownRefresh【常用】 6、onReachBottom【常用】 二、应用生命周期 1、onLaunch【常用】 2、onShow【常用】 3、onHide【常用】 三、组件生命周期 1、…...
LabVIEW机械产品几何精度质检系统
随着制造业的发展,对产品质量的要求越来越高,机械产品的几何精度成为衡量其品质的重要指标。为了提高检测效率和精度,开发了一套基于LabVIEW的几何精度质检系统,该系统不仅可以自动化地进行几何尺寸的测量,而且能实时分…...
java 检测图片链接有没有效
实际需求:发送调用微信图片发送接口前检验图片有效性。 在 Java 中,检测图片链接是否有效可以通过发送 HTTP 请求来判断服务器返回的状态码。通过 HttpURLConnection 类,可以轻松地实现这个功能。以下是一个简单的示例代码来检测图片链接是否…...
测试工程师学历路径:从功能测试到测试开发
现在软件从业者越来越多,测试工程师的职位也几近饱和,想要获得竞争力还是要保持持续学习。基本学习路径可以从功能测试-自动化测试-测试开发工程师的路子来走。 功能测试工程师: 1、软件测试基本概念: 学习软件测试的定义、目的…...
JavaEE---Spring IOC(2)
DI之三种注入 属性注入 构造方法注入 Setter注入 当程序中同一个类有多个对象的时候会报错解决方法如下: AutoWired和Resource的区别...
Oracle字符集
select userenv(language) from dual;如果显示如下,一个汉字占用两个字节 SIMPLIFIED CHINESE_CHINA.ZHS16GBK如果显示如下,一个汉字占用三个字节 SIMPLIFIED CHINESE_CHINA.AL32UTF8可以用以下语句查询一个汉字占用的字节长度 select lengthb(你) fr…...
RabbitMQ 常见使用模式详解
RabbitMQ 常见使用模式详解 RabbitMQ 是一个强大的消息队列中间件,支持多种消息通信模式,能够适应不同的业务场景。在这篇文章中,我们将详细介绍 RabbitMQ 的几种常见使用方法及其对应的场景。 1. 发布/订阅(Publish/Subscribe&a…...
JavaEE初阶——初识EE(Java诞生背景,CPU详解)
阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能帮到你! 目录 零:Java的发展背景介绍 一:EE的概念 二:计算机的构成 1:CU…...
iOS界面布局:屏幕尺寸与安全区域全面指南
引言 随着iPhone和iPad的更新迭代,iOS设备的屏幕尺寸和设计也在不断变化。无论是iPhone X系列的刘海屏,还是最新的iPhone 14,开发者都需要面对适配不同设备布局的问题。在项目开发中,导航栏、状态栏、TabBar的高度以及安全区域的…...
javascript-代码执行原理
js 是解释型语言 js 引擎执行流程 分为两个阶段: 语法分析执行阶段执行阶段涉及的数据结构: 调用栈。处理执行上下文和执行代码内存堆。给对象分配内存任务队列。暂存待执行的任务,分为宏任务队列和微任务队列语法分析 词法分析 > 语法分析 > 代码生成(字节码) …...
【C++ | tips】const Date* operator() const中这两个const有什么区别?他们的作用是什么?
const Date* operator&() const { return this; } 我们要明白operator&()这个函数是做什么的。 在C中,&操作符通常用于获取一个对象的地址。但是,有时候我们想要自定义这个行为,比如说,我们想要控制别人怎么获取…...
开放的数据时代:Web3和个人隐私的未来
在数字化和信息化的时代,数据隐私成为了公众关注的焦点。随着Web3技术的兴起,个人隐私保护进入了一个新的阶段。Web3作为去中心化的互联网架构,提出了对数据控制和隐私保护的新方案。本文将探讨Web3如何影响个人隐私的未来,并分析…...
Kafka 安全机制详解及配置指南
个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] 📱…...
渗透测试综合靶场 DC-2 通关详解
一、准备阶段 准备工具如Kali Linux,下载并设置DC-2靶场机。确保攻击机和靶机在同一网络段,通常设置为桥接模式或NAT模式。 1.1 靶机描述 Much like DC-1, DC-2 is another purposely built vulnerable lab for the purpose of gaining experience in …...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
