JVM:字节码文件,类的生命周期,类加载器
JVM:字节码文件,类的生命周期,类加载器
- = = = = = = = = = = = = = = = = 为什么要学这门课程 = = = = = = = = = = = = = = = =
- 1. 初识JVM
- 1.1. 什么是JVM
- 1.2. JVM的功能
- 1.3. 常见的JVM
- 2. 字节码文件详解
- 2.1. Java虚拟机的组成
- 2.2. 字节码文件的组成
- 2.2.1. 以正确的姿势打开文件
- 2.2.2. 字节码文件的组成
- 2.2.2.1 基本信息
- 2.2.2.2 常量池
- 2.2.3. 玩转字节码常用工具
- 2.3. 类的生命周期
- 2.4. 类加载器
- 2.4.1. 类加载器的分类
- 2.4.2. 双亲委派机制
- 2.4.3. 打破双亲委派机制
- 2.4.3.1. 自定义类加载器
- 2.4.3.2. 线程上下文类加载器
- 2.4.3.3. Osgi框架的类加载器(了解即可)
- 2.4.4. jDK9之后的类加载器


JAVA虚拟机属于java的核心特性。
= = = = = = = = = = = = = = = = 为什么要学这门课程 = = = = = = = = = = = = = = = =








课程安排


1. 初识JVM
1.1. 什么是JVM
JVM全称是Java Virtual Machine,中文译名Java虚拟机。


1.2. JVM的功能


虽然需要解释,不如C与C++。但是JAVA支持 跨平台 !
由于JVM需要实时解释虚拟机指令,不做任何优化性能不如直接运行机器码的C、C++等语言。
即时编译



1.3. 常见的JVM
| 名称 | 作者 | 支持版本 | 社区活跃度(github star) | 特性 | 适用场景 |
|---|---|---|---|---|---|
| HotSpot(Oracle JDK版) | Oracle | 所有版本 | 高(闭源) | 使用最广泛,稳定可靠,社区活跃 JIT支持 Oracle JDK默认虚拟机 | 默认 |
| HotSpot(Open jDK版) | Oracle | 所有版本 | 中(16.1k) | 同上 开源,Open jDK默认虚拟机 | 默认 对JDK有二次开发需求 |
| GraalVM | Oracle | 11, 17,19企业版支持8 | 高(18.7k) | 多语言支持高性能、JIT、 AOT支持 | 微服务、云原生架构 需要多语言混合编程 |
| Dragonwell jDK 龙井 | Alibaba | 标准版8,11,17 扩展版11,17 | 低(3.9k) | 基于OpenjDK的增强 高性能、bug修复、安全性提升 JWarmup、ElasticHeap、 Wisp特性支持 | 电商、物流、金融领域对性能要求比较高 |
| Eclipse OpenJ9(原IBM J9) | IBM | 8,11,17,19,20 | 低(3.1k) | 高性能、可扩展JIT、AOT特性支持 | 微服务、云原生架构 |
- 《Java虚 拟机规范》由Oracle制定,内容主要包含了Java虚拟机在设计和实现时需要遵守的规范,主要包含class字节码文件的定义、类和接口的加载和初始化、指令集等内容。
- 《Java虚拟机规范 》是对虚拟机设计的要求,而不是对Java设计的要求,也就是说虚拟机可以运行在其他的语言比如Groovy、Scala生成的class字节码文件之上。

C:\Users\HP>java -version
java version "19.0.2" 2023-01-17
Java(TM) SE Runtime Environment (build 19.0.2+7-44)
Java HotSpot(TM) 64-Bit Server VM (build 19.0.2+7-44, mixed mode, sharing)
对于HotSpot(Oracle JDK版) 最常用,以它为主要内容讲解!!


2. 字节码文件详解
2.1. Java虚拟机的组成
我现在编译了一个字节码文件



2.2. 字节码文件的组成
java虚拟机已经把字节码 加载与执行都处理完了,那还学什么!





2.2.1. 以正确的姿势打开文件
字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。








2.2.2. 字节码文件的组成
2.2.2.1 基本信息


●文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
●软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
| 文件类型 | 字节数 | 文件头 |
|---|---|---|
| JPEG (ipg) | 3 | FFD8FF |
| PNG (png) | 4 | 89504E47 (文件尾也有要求) |
| bmp | 2 | 424D |
| XML (xml) | 5 | 3C3F786D6C |
| AVI (avi) | 4 | 41 564920 |
| Java字节码文件(.class) | 4 | CAFEBABE |


●版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
比如:


两种方案:
- 升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)
- 将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求 建议采用

| 名称 | 作用 |
|---|---|
| Magic魔数 | 固定为0xCAFEBABE,不会改变 |
| 主版本号、副版本号 | 编译字节码文件的JDK版本 |
| 访问标识 | 标识是类还是接口、注解、枚举、模块标识public final abstract |
| 类、父类、接口索引 | 通过这些索引可以找到类、父类、接口的信息 |
2.2.2.2 常量池
保存了字符串常量、类或接口名、字段名主要在字节码指令中使用
字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。



名字也是常量,内容如果不一样就按常规方法存,但是名字和内容一样,其实常量池中已经有这个常量了,常量池中一般不重复,所以不用中转。
名字也作为一个值来储存,比如A = “abc” 什么以上 在一个地址 地址里面存了A 另外一个地址存"abc" 这需要2个地址 如果abc = “abc” 那么 abc 直接就存在那个地址里面

●字节码中的方法区 域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
我们打开字节码文件




注意 : 对于iload_1这个 是把数值复制了一份 而不是删掉
public class Demo1 {public static void main(String[] args) {int i = 0;i =i++;System.out.println(i); //0}
}




答案是0,我通过分析字节码指令发现,i+ +先把0取出来放入临时的操作数栈中,接下来对i进行加1,i变成了1,最后再将之前保存的临时值0放入i,最后i就变成了0。

2.2.3. 玩转字节码常用工具


在IDEA安装插件!
build——recompile
必须选择自己的文件 且查看最新的要重新编译!
如果我们的程序已经在运行中,我们可不可以查看字节码文件


- dump类的全限定名: dump已加载类的字节码文件到特定目录。
- jad类的全限定名:反编译已加载类的源码。


2.3. 类的生命周期
类的生命周期描述了一个类加载、使用、卸载的整个过程

对于使用来说 我们最熟悉——new 或者反射等。
有的可能分的比较细,7个阶段,是因为把连接拆成了验证、准备、和解析。
加载阶段:
- 加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。
不同渠道是什么?

-
类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。
-
类加载器在加载完类之后, Java虚拟机会将字节码中的信息保存到内存的方法区中。
生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。

- 同时,Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。
作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8及之后)。

推荐使用JDK自带的hsdb工具查看Java虚拟机内存信息。工具位于JDK安装目录下lib文件夹中的sa-jdijar中。
java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

连接阶段:

连接阶段——验证
验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。





主版本号不能高于运行环境主版本号,如果主版本号相等,副版本号也不能超过。
连接阶段——准备阶段:
●准备阶段为静态变量(static) 分配内存并设置初始值。
●注意:本章涉及到的内存结构只讨论JDK8及之后的版本,8之前的版本后续章节详述。


为什么要默认值?

但是!final 修饰呢?又是另外一种情况!
这个例子就说明,他没有赋值初值为 0 的过程!

连接阶段——解析阶段:
●解析阶段主要是将常量池中的符号引用替换为直接引用。
●符号引用就是在字节码文件中使用编号来访问常量池中的内容。


初始化阶段
●初始化阶段会执行静态代码块中的代码,并为静态变量赋值。
●初始化阶段会执行字节码文件中clinit部分的字节码指令。





以下几种方式会导致类的初始化:
- 访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化。
- 调用Class.forName(String className)。
- new一个该类的对象时。
- 执行Main方法的当前类。




第二个测试用例:

结果打印:初始化了…

第三个测试用例:

我们找到main所在的类:Demo5 初始化! 然后用到了new Demo6 初始化6

好!我们现在看看这两道大题能不能轻松拿下!

public class Demo1 {public static void main(String[] args) {System.out.println("A");new Demo1();new Demo1();}public Demo1(){System.out.println("B");}{System.out.println("C");}static {System.out.println("D");}
}
DACBCB
因为实例代码块会在构造方法之前执行
类的生命周期初始化阶段
clinit指令 在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行的。
1.无静态代码块且无静态变量赋值语句。
2.有静态变量的声明,但是没有赋值语句。
3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。





2.4. 类加载器
类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。




2.4.1. 类加载器的分类
类载器分为两类
- Java代码中实现
- Java虚拟机底层源码实现

类加载器的设计JDK8和8之后的版本差别较大,JDK8及之前的版本中默认的类加载器有如下几种:

类加载器的分类启动类加载器
●启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C+ +编写的类加载器。
●默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar, resources.jar等。
通过启动类加载器去加载用户jar包:
- 放入jre/lib下进行扩展
- 不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载
- 使用参数进行扩展
- 推荐,使用-Xbootclasspath/a:jar包目录/jar包名进行扩展

类加载器的分类默认类加载器
●扩展类加载器和应用程序类加载器都是JDK中提供的、 使用Java编写的类加载器。
●它们的源码都位于sun.misc.Launcher中, 是一个静态内部类。继承自URLClassLoader。 具备通过目录或者指定jar包将字节码文件加载到内存中。

通过扩展类加载器去加载用户jar包:
- 放入/jre/lib/ext下进行扩展
- 不推荐,尽可能不要去更改JDK安装目录中的内容
- 使用参数进行扩展
- 推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录
类加载器的分类应用程序类加载器

验证一下:


2.4.2. 双亲委派机制

双亲委派机制有什么用!
- 保证类加载的安全性
- 通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。
- 避免重复加载
- 双亲委派机制可以避免同一个类被多次加载。
双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载。



我们可以在代码中展示一下这个类:
首先,在Java中如何使用代码的方式去主动加载一个类呢?
- 方式1:使用Class.forName方法,使用当前类的类加载器去加载指定的类。
- 方式2:获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。
例如:

- 应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空,但是在代码逻辑上,扩展类加载器依然会把启动类加载器当成父类加载器处理。
- 启动类加载器使用C++编写,没有父类加载器。



2.4.3. 打破双亲委派机制
虽然说,我们的双亲委派机制很牛!但是我们在一些情况下,我们需要打破这个机制才能实现我们想要的功能。
2.4.3.1. 自定义类加载器
-
自定义类加载器并且重写loadClass方法,就可以将双亲委派机制的代码去除
-
Tomcat通过这种方式实现应用之间类隔离,《面试篇》 中分享它的做法
-
一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
-
如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。


- 先来分析ClassLoader的原理 ,ClassLoader中包含 了4个核心方法。
- 双亲委派机制的核心代码就位于loadClass方法中。
public Class<?> loadClass(String name)
protected Class<?> findClass(String name)
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
protected final void resolveClass(Class<?> c)


2.4.3.2. 线程上下文类加载器
- 利用上下文类加载器加载类,比如JDBC和JNDI等

spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。
spi的工作原理:

- 在ClassPath路径下的META-INF/services文件夹中,以接口的全限定名来命名文件名, 对应的文件里面写该接口的实现。
- 使用ServiceLoader加载实现类。

2.4.3.3. Osgi框架的类加载器(了解即可)
- 历史上0sgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载

2.4.4. jDK9之后的类加载器





相关文章:
JVM:字节码文件,类的生命周期,类加载器
JVM:字节码文件,类的生命周期,类加载器 为什么要学这门课程 1. 初识JVM1.1. 什么是JVM1.2. JVM的功能1.3. 常见的JVM 2. 字节码文件详解2.1. Java虚拟机的组成2.2. 字节码文件的组成2.2.1. 以正确的姿势打开文…...
【IPC】消息队列
1、IPC对象 除了最原始的进程间通信方式信号、无名管道和有名管道外,还有三种进程间通信方式,这 三种方式称之为IPC对象 IPC对象分类:消息队列、共享内存、信号量(信号灯集) IPC对象也是在内核空间开辟区域,每一种IPC对象创建好…...
内网穿透工具NPS(保姆级教程)
前言: 有时候我们受限于硬件设备和网络的的问题,无法将内网的大容量、高性能存储设备或计算设备对外访问。这个时候就会变的特别苦恼,上云呢成本太大,不用云呢公网又无法直接访问,这个时候怎么办呢,NPS它来…...
最长公共子序列问题
构造最长公共子序列为什么要这样构造序列 for(int i1;i<n;i){int k;cin>>k;b[k]i;}for(int i1;i<n;i){int k;cin>>k;a[i]b[k];}并且为什么要求上升序列,是有什么数学知识包含在其中吗? 为什么在求最长公共子序列时,f[mid]大…...
服务器数据恢复—热备盘同步中断导致Raid5数据丢失的数据恢复案例
服务器数据恢复环境: 某单位一台服务器上有一组raid5阵列,该raid5阵列有15块成员盘。上层是一个xfs裸分区,起始位置是0扇区。 服务器故障&检测: 服务器raid5阵列中有硬盘性能表现不稳定,但是由于管理员长时间没有关…...
桥接模式-C++实现
桥接模式是一种结构型设计模式,它是将抽象部分和实现部分隔离,通过组合关系将抽象部分和实现部分解耦,使它们可以独立变化。 因此,桥接模式可以很好的处理两个或两个以上维度的变化。 举一个例子说明: 假设我们现在…...
PHP字符串函数的解析
在PHP中,字符串是一种常见的数据类型,用于存储和操作文本数据。PHP提供了丰富的字符串函数,用于执行各种字符串操作,包括截取、连接、替换、搜索等。在这篇文章中,我们将深入解析一些常用的PHP字符串函数,以…...
科研学习|研究方法——使用python强大的Statsmodel 执行假设检验和线性回归
如果你使用 Python 处理数据,你可能听说过 statsmodel 库。 Statsmodels 是一个 Python 模块,它提供各种统计模型和函数来探索、分析和可视化数据。该库广泛用于学术研究、金融和数据科学。 在本文中,我们将介绍 statsmodel 库的基础知识、如…...
设计模式——责任链模式
文章目录 责任链模式的定义场景示例责任链模式实现方案责任链模式扩展责任链模式的优缺点责任链模式在框架源码中的应用 责任链模式的定义 责任链模式又称职责链模式,是一种行为型设计模式。官方描述:使多个对象都有机会处理请求,从而避免请…...
nginx得if语句内proxy_pass不允许携带url部分,如何处理
在nginx中,proxy_pass指令不能直接携带URL部分。但是,可以使用rewrite指令结合正则表达式来处理URL部分。 下面是一个示例配置,演示如何使用rewrite指令将URL中的某个部分进行替换后传递给后端服务器: location /v100/{proxy_…...
CentOS7设置 redis 开机自启动
CentOS7设置 redis 开机自启动 步骤1.创建redis.service文件2.重新加载所有服务3.设置开机自启动4.自由地使用linux系统命令4.1.启动 Redis 服务4.2.查看 Redis 状态(-l:查看完整的信息)4.3.停止 Redis 服务4.4.重启 Redis 服务 步骤 如果你傲娇,不想拷贝࿰…...
C++虚函数(定义,作用,原理,案例)
一.定义: C的虚函数是在父类(基类)中声明的的函数,它可在子类(派生类)中重写。二.作用 虚函数的目的是实现多态性,即在程序运行时根据对象的实际类型确定调用哪个函数。三.使用方法: 在基类中声明虚函数时,需要在函…...
C#中.NET 6.0 控制台应用通过EF访问新建数据库
目录 一、 操作步骤 二、编写EF模型和数据库上下文 三、 移植(Migrations)数据库 四、编写应用程序并运行 前文已经说过.NET Framework4.8 控制台应用通过EF访问新建数据库,这里的数据据库要根据事先编写好的EF模型、经过一番操作&#x…...
conda创建pytorch环境报错
昨天训练数据的时候,发现Anaconda占用C盘达到了20G(暑假在cmd状态下安装的,默认下载到了C盘),心道再创建几个环境,C盘就要爆红了,于是重装Anaconda到了D盘,不过之后的初始化并不顺利…...
数据结构-插入排序实现
文章目录 1、描述2、代码实现3、结果4、复杂度 1、描述 待排序的数组分为已排序、未排序两部分; 初始状态时,仅有第一个元素为已排序序列,第一个以外的元素为未排序序列; 此后遍历未排序序列, 将元素逐一插入到已排序的序列中&am…...
CGlib动态代理和JDK动态代理
CGlib代理模式是一种基于字节码操作的代理模式,它通过生成被代理类的子类来实现代理功能。 CGlib通过继承被代理类,生成一个代理类的子类,并重写父类的方法,在方法的前后插入相应的代理逻辑。这种方式不需要被代理类实现接口&…...
分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测
分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测 目录 分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO…...
Python OpenCV 视频抽帧处理并保存
上篇文章中基于OpenCV实现图像处理后,类似的,也可以对视频进行处理。OpenCV库可以将视频的每一帧读取出来,然后对每一帧图像做相应的操作,并保存成新的视频。 1. 读取视频,获取相关参数 import cv2 import numpy as…...
英伟达AI布局的新动向:H200 GPU开启生成式AI的新纪元
英伟达Nvidia是全球领先的AI计算平台和GPU制造商,近年来一直在不断推出创新的AI产品和解决方案,为各行各业的AI应用提供强大的支持。 最近,英伟达在GTC 2023大会上发布了一款专为训练和部署生成式AI模型的图形处理单元(GPU&#…...
Windows11 python3.12 安装pyqt6 pyqt6-tools
Windows11 python3.12 安装pyqt6比较容易,但pyqt6-tools一直安装不上去。出错信息如下: (venv) PS D:\python_project\pyqt6> pip install pyqt6-tools Collecting pyqt6-toolsUsing cached pyqt6_tools-6.4.2.3.3-py3-none-any.whl (29 kB) Collec…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
