本文已参与「新人创作礼」活动,一起开启掘金创作之路。
JVM的概览
为何要了解JVM
- The Java Virtual Machine is the cornerstone of the Java platform.
- The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time.
- The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the
class
file format.
JVM,JDK,JRE的关系
由图可知,JDK包含JRE,JRE包含了JVM。
关于语言
语言类型 | 定义 | 特点 | 示例 |
---|---|---|---|
编译型 | 使用特定的编译器,针对不同的平台,将源代码一次性的编译成该平台可执行的程序格式 | 执行速度快,效率高; 依靠编译器,跨平台差 | C,C++,GoLang |
解释型 | 使用特定的解释器对源程序逐行解释成平台机器码并立即执行,在执行时才被一行一行解释。 | 执行速度慢,效率低; 依靠解释器,跨平台友好 | Python,JavaScript |
编译型&解释型 | 由javac等编译器编译成JVM能够识别的文件格式,再由JVM解释或编译成具体的平台执行的程序格式 | 执行速度快,效率高,跨平台友好 | Java |
关于冯诺依曼
既然Java官方将JVM称为一个抽象计算机,那么肯定满足冯诺依曼的体系,在JVM的探索中我们期望可以将JVM的相关结构进行验证。
冯诺依曼结构 | JVM中结构 | 备注 |
---|---|---|
Input | 类加载器 | 加载class文件 |
Output | 平台适配的机器码 | |
Memory | 分割成多个区域的存储区,运行时数据区 |
JVM类加载过程
编译(从.java到.class)
词法分析器语法分析器语义分析器代码生成器
Java源码
通过词法分析器生成token流
;token流
通过语法分析器生成语法树
;通过语义分析器生成注解语法树
;再通过代码生成器生成字节码文件
。
class文件解析
Demo
package com.bayitalk.clazz;
/**
* <p> 测试类文件格式
*
* @author Morris
* @version 1.0
*/
public class TClass {
private final static int a = 20;
private static int b = 129;
private int c = 12;
private static String comment = "This is Test class";
public String getComment(){
return comment;
}
public static void main(String[] args) {
System.out.println(a);
}
}
复制代码
javac编译成class
class文件格式
Chapter 4. The class
File Format
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
复制代码
例如magic的u4中:u表示无符号,4表示4个字节,在十六进制中,两个数字表示1个字节。
structure | 大小 | 含义 | 备注 |
---|---|---|---|
magic | u4 | 魔法标识,用来标识class文件格式 | 0xCAFBABE是固定值 |
minor_version | u2 | java的小版本号 | |
major_version | u2 | java的大版本号 | 52是java8 |
constant_pool_count | u2 | 常量池中常量的个数 | 由于下标是从1开始计算的,因此实际个数=count-1 |
constant_pool[constant_pool_count-1]; | cp_info | 常量池具体内容 | |
access_flags | u2 | 访问标识 |
javap 反编译
javap -v -p TClass.class > TClass.txt
复制代码
Classfile /Users/huxingbo/Documents/morris_projects/jvm-stu/src/main/java/com/bayitalk/clazz/TClass.class
Last modified 2022-5-9; size 717 bytes
MD5 checksum 19506192bfa2c2d0aeb1fd37bdb5db45
Compiled from "TClass.java"
public class com.bayitalk.clazz.TClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#30 // com/bayitalk/clazz/TClass.c:I
#3 = Fieldref #5.#31 // com/bayitalk/clazz/TClass.comment:Ljava/lang/String;
#4 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Class #34 // com/bayitalk/clazz/TClass
#6 = Methodref #35.#36 // java/io/PrintStream.println:(I)V
#7 = Fieldref #5.#37 // com/bayitalk/clazz/TClass.b:I
#8 = String #38 // This is Test class
#9 = Class #39 // java/lang/Object
#10 = Utf8 a
#11 = Utf8 I
#12 = Utf8 ConstantValue
#13 = Integer 20
#14 = Utf8 b
#15 = Utf8 c
#16 = Utf8 comment
#17 = Utf8 Ljava/lang/String;
#18 = Utf8 <init>
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 getComment
#23 = Utf8 ()Ljava/lang/String;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 <clinit>
#27 = Utf8 SourceFile
#28 = Utf8 TClass.java
#29 = NameAndType #18:#19 // "<init>":()V
#30 = NameAndType #15:#11 // c:I
#31 = NameAndType #16:#17 // comment:Ljava/lang/String;
#32 = Class #40 // java/lang/System
#33 = NameAndType #41:#42 // out:Ljava/io/PrintStream;
#34 = Utf8 com/bayitalk/clazz/TClass
#35 = Class #43 // java/io/PrintStream
#36 = NameAndType #44:#45 // println:(I)V
#37 = NameAndType #14:#11 // b:I
#38 = Utf8 This is Test class
#39 = Utf8 java/lang/Object
#40 = Utf8 java/lang/System
#41 = Utf8 out
#42 = Utf8 Ljava/io/PrintStream;
#43 = Utf8 java/io/PrintStream
#44 = Utf8 println
#45 = Utf8 (I)V
{
private static final int a;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: int 20
private static int b;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
private int c;
descriptor: I
flags: ACC_PRIVATE
private static java.lang.String comment;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
public com.bayitalk.clazz.TClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 12
7: putfield #2 // Field c:I
10: return
LineNumberTable:
line 9: 0
line 14: 4
public java.lang.String getComment();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: getstatic #3 // Field comment:Ljava/lang/String;
3: areturn
LineNumberTable:
line 19: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 20
5: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
8: return
LineNumberTable:
line 23: 0
line 24: 8
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 129
3: putstatic #7 // Field b:I
6: ldc #8 // String This is Test class
8: putstatic #3 // Field comment:Ljava/lang/String;
11: return
LineNumberTable:
line 12: 0
line 16: 6
}
SourceFile: "TClass.java"
复制代码
加载(从class到jvm)
Chapter 5. Loading, Linking, and Initializing
加载.class的方式
加载方式 | 示例 | 备注 |
---|---|---|
本地加载 | ||
网络加载 | Web Applet,小程序 | |
归档文件加载 | jar,war | |
数据库加载 | JSP从数据库提取class文件 | 少见 |
动态编译,运行时计算 | 动态代理 | |
加密文件 | 防class反编译 |
类生命周期:装载(Load) 链接(Link)初始化(Initialize) 使用(Use)卸载(Upload)
类加载机制:虚拟机将.class文件加载到内存对数据进行校验、转换、解析、初始化变成虚拟机可以直接使用的java类型:java.lang.Class
装载(Load)
-
通过类加载器(寻找器,独立于JVM之外的实现,可以决定如何加载)获取类的二进制流,一般而言,Java程序员能够在这一步通过自定义加载器,字节码增强,java agent方式来增强类
-
字节流中的静态存储结构会转化成方法区的运行时数据结构,分配内存(此时内存中有数据位置,但是无法访问)
-
在java堆中生成被加载类的
java.lang.Class
对象作为数据访问入口 -
JIT的热点代码不再这个阶段进入方法区
位置 | 数据 |
---|---|
方法区 | 类信息、常量、静态变量 |
堆 | java.lang.Class作为数据的访问入口 |
链接(Link)
验证(Verification)
验证贯穿着整个类加载周期,以保证虚拟机不受危害
验证 | 详情 | 备注 |
---|---|---|
文件格式的验证 | 1.文件数据结构格式 2.版本,保证字节流能被正确存储方法区 | |
元数据验证 | 语义验证,java语法,方法区的存储验证 | |
字节码验证 | 1.数据流控制流的分析验证 2.类的方法体,安全性验证 3.栈数据类型操作码合法性检查,自身大小,不是深度 4.字节码跳转是否合法(例如常量池的引用跳转) | |
符号引用验证 | 符号引用转变为直接引用的合法性 |
准备(Preparation)
- 为静态变量分配内存,初始化默认值,并不执行Java代码。默认值而非初始值。
- final static修饰的在准备期间会初始化,被ConstantValue修饰的变量,会从常量池中找值进行赋值。常量池中只有基础数据类型和String类型
- 准备阶段只需要在初始化之前就可以发生。
解析(Resolution)
Java 虚拟机指令 anewarray、checkcast、getfield、getstatic、instanceof、 invokedynamic、invokeinterface、invokespecial、invokestatic、 invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic对运行时常量池进行符号引用。执行任何这些指令都需要解析其符号引用。
-
解析是从运行时常量池中的符号引用动态确定具体值的过程。
-
由于同一个符号引用可能被多次解析,因此JVM会对解析的结果进行缓存。除了
invokedynamic
指令,后续被用到java8的lambda表达式中。 -
Overriding也与此阶段相关
初始化( Initialization)
- 执行类构造器的方法,赋初始值,声明类变量指定初始值
- 静态代码块为类变量赋值
- 如果没有Load,Link,则先Load、Link
- 如果该类父类还没有初始化,先初始化父类
- 依次执行初始化语句
JVM是多线程进行初始化的,使用初始化锁LC控制。
使用(Use)
只有主动引用了类,才会初始化类。
主动引用
- 创建类的时候,new
- 访问或使用接口和类的静态变量
- 调用类的静态方法
- 反射
- 初始化子类时,先初始化父类
- 启动类
被动引用
- 引用父类的静态字段,只初始化父类,子类不出适合
- 定义类数组,不会类初始化
- 引用类static和final同时修饰的常量,不会引起类初始化
卸载
- 类的所有实例被回收
- 加载类的ClassLoader被回收
- 该类的java.lang.Class对象没有被引用