JVM-基础篇
一. JVM
1. 初识JVM
什么是JVM?
JVM 全程 Java Virtual Machine,中文译名Java虚拟机
JVM本质上是一个运行在计算机上的程序,他的</职责是运行Java字节码文件
2. JVM的功能
- 解释和运行
- 对字节码文件中的指令,实时的解释成机器码,让计算机执行
- 内存管理
- 自动为对象、方法等分配内存
- 自动的垃圾回收机制,回收不再利用的对象
- 即时编译
- 对热点代码进行优化,提升执行效率
即时编译
Java语言如果不做任何优化,性能不如C、C++等语言
Java需要实时解释,主要是为了支持跨平台特性
由于JVM需要实时解释虚拟机指令,不做任何优化性能不如直接运行机器码的C、C++等语言

常见的JVM
| 名称 | 作者 | 支持版本 | 社区活跃度 (github star) | 特性 | 适用场景 |
|---|---|---|---|---|---|
| HotSpot (Oracle JDK版) | Oracle | 所有版本 | 高(闭源) | 使用最广泛,稳定可靠,社区活跃 JIT支持 Oracle JDK默认虚拟机 | 默认 |
| otSpot (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 | 8,11,17,19,20 | 低(3.1k) | 高性能、可扩展 JIT、AOT特性支持 | 微服务、云原生架构 |
HotSpot的发展历程
总结
JVM到底是什么?
JVM 全称是 Java Virtual Machine,中文译名 Java虚拟机,是一个运行在计算 机上的程序,他的职责是运行Java字节码文件
JVM的三大核心功能是什么?
JVM 包含内存管理、解释执行虚拟机指令、即时编译三大功能。
常见的JVM虚拟机有哪些?
常见的JVM有HotSpot、GraalVM、OpenJ9等,另外DragonWell龙井JDK也 提供了一款功能增强版的JVM。其中使用最广泛的是HotSpot虚拟机
二. 字节码文件详解
1. Java虚拟机的组成

2. 字节码文件的组成
字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读
推荐使用 jclasslib工具查看字节码文件,Github地址: https://github.com/ingokegel/jclasslib
字节码文件一般包含5个部分
基础信息
魔数、字节码文件对应的Java版本号 访问标识(public final等等) 父类和接口

常量池
保存了字符串常量、类或接口名、字段名 主要在字节码指令中使

字段
当前类或接口声明的字段信息

方法
当前类或接口声明的方法信息 字节码指令

属性
类的属性,比如源码的文件名 内部类的列表等

3. 基本信息
Magis魔数

- 文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容
- 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错
- Java字节码文件中,将文件头称为magic魔数
| 文件类型 | 字节数 | 文件头 |
|---|---|---|
| JPEG(jpg) | 3 | FFD8FF |
| PNG(png) | 4 | 89504E47(文件尾也有要求) |
| bmp | 2 | 424D |
| XML(xml) | 5 | 3C3F786D6C |
| AVI(avi) | 4 | 41564920 |
| Java字节码文件(.class) | 4 | CAFEBABE |
主副版本号
- 主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了 45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同 版本的标识,一般只需要关心主版本号
- 版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容
1.2之后大版本号计算方法就是: 主版本号 – 44 比如主版本号52就是JDK1.8
主版本号不兼容导致的错误

解决方案
- 升级JDK版本
- 将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求
4. 常量池
- 字节码文件中常量池的作用:避免相同的内容重复定义,节省空间
- 常量池中的数据都有一个编号,编号从1开始,在字段或字节码指令中可以通过编号快速找到对应的数据
- 字节码指令通过编号引用到常量池的过程称为符号引用
5. 方法
- 字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中
- 操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置
解析 int i = 0; int j = i + 1;
- iconst_0: 把0放入操作上栈
- istore_1: 从操作数栈取出放入 局部变量表1号位置
- iload_1: 将局部变量表1中的数 据放入操作数栈
- iconst_1: 将常量1放入操作数栈
- iadd: 将操作数栈顶部的两个 数据进行累加,结果放入栈中
- istore_2: 将相加后的结果取出放入局部变量表2号位置
- return: 方法结束、返回
解析 i = 0; i = i++;
解析 i = 0; i = ++i;
int i = 0; i = i++; 最终i的值是多少?
最终i的值是0,通过分析字节码文件发现,i++ 是先把0取出来放入操作数栈中,接下来在局部变量表中对i进行加1,i变成了1,最近再将之前操作数栈中的0取出来放入i,最后i就变成了0
6. 字节码文件常用工具
- javap -v 命令
- jclasslib插件
- 阿里arthas
javao -v命令
- javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内 容
- 直接输入javap查看所有参数
- 输入javap -v 字节码文件名称 查看具体的字节码信息。(如果jar包需要先使用 jar –xvf 命令解压)
jclasslib idea插件
jclasslib也有Idea插件版本,建议开发时使用Idea插件版本,可以在代码编译之后实时看到字节码 文件内容
首先应该下载并安装插件
编译完代码后即可打开字节码文件
阿里arthas
- Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修 改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率
- 官网:https://arthas.aliyun.com/doc/
- dump 类的全限定名:dump已加载类的字节码文件到特定目录
- ad 类的全限定名: 反编译已加载类的源码
三. 类的生命周期
1. 加载阶段
加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。 程序员可以使用Java代码拓展的不同的渠道

类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中

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

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

对于开发者来说,只需要访问堆中的Class对象而不需要访问方法区中所有信息,这样Java虚拟机就能很好地控制开发者访问数据的范围
2. 连接阶段
验证
连接的第一个环节是验证,验证的主要目的是检查Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与
主要包含4个部分
- 文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求
- 元信息验证,例如类必须有父类(super不能为空)
- 验证程序执行指令的语义,比如方法内的指令执行到一半强行跳转到其他方法中去
- 符号引用验证,例如是否访问了其他类中private的方法等
准备
准备阶段为静态变量(static)分配内存并设置初始值
准备阶段只会给静态变量赋初始值,而每一种基本数据类型和引用数据类型都有其初始值
| 数据类型 | 初始值 |
|---|---|
| int | o |
| long | 0L |
| short | 0 |
| char | ‘\u0000’ |
| byte | 0 |
| boolean | false |
| double | 0.0 |
| 引用数据类型 | null |
final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值
解析
- 解析阶段主要是将常量池中的符号引用替换为直接引用
- 直接引用不在使用编号,而是使用内存中地址进行访问具体的数据