阅读前可先参考
JVM -- JVM内存结构:程序计数器、虚拟机栈、本地方法栈、堆、方法区(二)
一、类文件结构
编写一个简单的 HelloWorld.java 文件,将其编译为 HelloWorld.class 文件
HelloWorld.java 文件
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
HelloWorld.class 文件
package com.mycompany;
public class HelloWorld {
public HelloWorld() {
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
在Linux上执行 od -t xC HelloWorld.class 命令,输出如下
[root@eslinux1 jvmdemo]# od -t xC HelloWorld.class
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79
0000160 63 6f 6d 70 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
0000220 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
0000260 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00
0000400 20 00 21 01 00 18 63 6f 6d 2f 6d 79 63 6f 6d 70
0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00
0000440 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63
0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79
0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61
0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61
0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69
0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74
0000600 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
0000660 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01
0000700 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0000720 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
0000740 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00 00
0000760 37 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6 00
0001000 04 b1 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00
0001020 00 05 00 08 00 06 00 0b 00 00 00 0c 00 01 00 00
0001040 00 09 00 10 00 11 00 00 00 01 00 12 00 00 00 02
0001060 00 13
0001062
JVM规范官网
如JDK8
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];
}
1、魔数
0~3 字节,表示它是否是【class】类型的文件
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
2、版本
4~7 字节,表示类的版本 00 34(52) 表示是 Java 8
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
3、常量池
Chapter 4. The class File Format
Constant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
8~9 字节,表示常量池长度,00 22 (34) 表示常量池有 #1~#33项,注意 #0 项不计入,也没有值
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
第#1项 0a 表示一个 Method 信息,00 06 和 00 14(20) 表示它引用了常量池中 #6 和 #21 项来获得 这个方法的【所属类】和【方法名】
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
第#2项 09 表示一个 Field 信息,00 15(21)和 00 16(22) 表示它引用了常量池中 #21 和 # 22 项 来获得这个成员变量的【所属类】和【成员变量名】
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
第#3项 08 表示一个字符串常量名称,00 18(23)表示它引用了常量池中 #23 项
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
第#4项 0a 表示一个 Method 信息,00 18(24) 和 00 19(25) 表示它引用了常量池中 #24 和 #25 项来获得这个方法的【所属类】和【方法名】
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
第#5项 07 表示一个 Class 信息,00 1a(26) 表示它引用了常量池中 #26 项
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
第#6项 07 表示一个 Class 信息,00 1b(27) 表示它引用了常量池中 #27 项
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
第#7项 01 表示一个 utf8 串,00 06 表示长度,3c 69 6e 69 74 3e 是【 】
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
第#8项 01 表示一个 utf8 串,00 03 表示长度,28 29 56 是【()V】其实就是表示无参、无返回值
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
第#9项 01 表示一个 utf8 串,00 04 表示长度,43 6f 64 65 是【Code】
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
第#10项 01 表示一个 utf8 串,00 0f(15) 表示长度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 是【LineNumberTable】
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
第#11项 01 表示一个 utf8 串,00 12(18) 表示长度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65是【LocalVariableTable】
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
第#12项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【this】
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79
第#13项 01 表示一个 utf8 串,00 1a(26) 表示长度,是【Lcn/mycompany/HelloWorld;】
0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79
0000160 63 6f 6d 70 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
第#14项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【main】
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
第#15项 01 表示一个 utf8 串,00 16(22) 表示长度,是【([Ljava/lang/String;)V】其实就是参数为 字符串数组,无返回值
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
0000220 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
第#16项 01 表示一个 utf8 串,00 04 表示长度,是【args】
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
第#17项 01 表示一个 utf8 串,00 13(19) 表示长度,是【[Ljava/lang/String;】
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
0000260 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
第#18项 01 表示一个 utf8 串,00 0a(10) 表示长度,是【SourceFile】
0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c
第#20项 01 表示一个 utf8 串,00 0f(15) 表示长度,是【HelloWorld.java】
0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c
第#21项 0c 表示一个 【名+类型】,00 07 00 08 引用了常量池中#7 #8 两项
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00
第#22项 07 表示一个 Class 信息,00 1c(28) 引用了常量池中#28 项
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
第#23项 0c 表示一个 【名+类型】,00 1d(29) 00 1e (30)引用了常量池中 #29#30两项
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
第#24项 01 表示一个 utf8 串,00 1c(12) 表示长度,是【hello world】
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00
第#25项 07 表示一个 Class 信息,00 20(32) 引用了常量池中#32 项
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00
0000400 20 00 21 01 00 18 63 6f 6d 2f 6d 79 63 6f 6d 70
第#26项 0c 表示一个 【名+类型】,00 21(33) 00 22(34)引用了常量池中 #33 #34 两项 0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00
第#27项 01 表示一个 utf8 串,00 1b(27) 表示长度,是【cn/mycompany/HelloWorld
0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00
0000440 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63
0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79
第#28项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/Object】
0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79
0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61
第#29项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/System】
0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61
0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61
第#30项 01 表示一个 utf8 串,00 03 表示长度,是【out】
0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61
0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69
第#31项 01 表示一个 utf8 串,00 15(21) 表示长度,是【Ljava/io/PrintStream;】
0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69
0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74
第#32项 01 表示一个 utf8 串,00 13(19) 表示长度,是【java/io/PrintStream】
0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74
0000600 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67
第#33项 01 表示一个 utf8 串,00 07 表示长度,是【println】
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
第#34项 01 表示一个 utf8 串,00 15(21) 表示长度,是【(Ljava/lang/String;)V】
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
4、访问标识与继承信息
21 表示该 class 是一个类,公共的
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
05 表示根据常量池中 #5 找到本类全限定名
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
06 表示根据常量池中 #6 找到父类全限定名
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
表示接口的数量,本类为 0
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
Chapter 4. The class File Format
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private ; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static . |
ACC_FINAL | 0x0010 | Declared final ; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile ; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient ; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM | 0x4000 | Declared as an element of an enum . |
5、Field 信息
表示成员变量数量,本类为 0
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
Chapter 4. The class File Format
FieldType term | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L ClassName ; | reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension |
6、Method 信息
表示方法数量,本类为 2
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
0000660 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01
0000700 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0000720 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
0000740 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00 00
一个方法由 访问修饰符,名称,参数描述,方法属性数量,方法属性组成
红色代表访问修饰符(本类中是 public)
蓝色代表引用了常量池 #07 项作为方法名称
绿色代表引用了常量池 #08 项作为方法参数描述
黄色代表方法属性数量,本方法是 1
红色代表方法属性
(1)00 09 表示引用了常量池 #09 项,发现是【Code】属性
(2)00 00 00 2f 表示此属性的长度是 47
(3)00 01 表示【操作数栈】最大深度
(4)00 01 表示【局部变量表】最大槽(slot)数
00 00 00 05 表示字节码长度,本例是 5
2a b7 00 01 b1 是字节码指令
00 00 00 02 表示方法细节属性数量,本例是 2
00 0a 表示引用了常量池 #10 项,发现是【LineNumberTable】属性
00 00 00 06 表示此属性的总长度,本例是 6
00 01 表示【LineNumberTable】长度
00 00 表示【字节码】行号 00 04 表示【java 源码】行号
00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性
00 00 00 0c 表示此属性的总长度,本例是 12
00 01 表示【LocalVariableTable】长度
00 00 表示局部变量生命周期开始,相对于字节码的偏移量
00 05 表示局部变量覆盖的范围长度
00 0c 表示局部变量名称,本例引用了常量池 #12 项,是【this】
00 0d 表示局部变量的类型,本例引用了常量池 #13 项,是 【Lcn/mycompany/HelloWorld;】
00 00 表示局部变量占有的槽位(slot)编号,本例是 0
二、字节码指令
(一)字节码指令基础
Chapter 6. The Java Virtual Machine Instruction Set
(1)public com.mycompany.HelloWorld();
构造方法的字节码指令
2a b7 00 01 b1
2a => aload_0 加载 slot 0 的局部变量,即 this,作为下面的 invokespecial 构造方法调用的参数
b7 => invokespecial,预备调用构造方法
00 01 引用常量池中 #1 项,即【 Method java/lang/Object."":()V 】
b1 表示返回
(2)public static void main(java.lang.String[]);
主方法的字节码指令
b2 00 02 12 03 b6 00 04 b1
b2 => getstatic 用来加载静态变量
00 02 引用常量池中 #2 项,即【Field java/lang/System.out:Ljava/io/PrintStream;】
12 => ldc 加载参数
03 引用常量池中 #3 项,即 【String hello world】
b6 => invokevirtual,预备调用成员方法
00 04 引用常量池中 #4 项,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
b1 表示返回
(二)javap 工具
Oracle 提供了 javap 工具来反编译 class 文件
首先切换到 HelloWorld.class文件所在目录,执行如下命令
javap -v HelloWorld.class
cd D:\Java\JavaProject\xx\com\mycompany
javap -v HelloWorld.class
控制台输出如下
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/HelloWorld.class //类文件名字以及路径
Last modified 2021-9-27; size 562 bytes //最后修改时间
MD5 checksum 56139c042931911e7cea84a4ece0987c
Compiled from "HelloWorld.java" //源文件;从哪个java文件编译而来
public class com.mycompany.HelloWorld
minor version: 0
major version: 52 //JDK版本号,52代表JDK8
flags: ACC_PUBLIC, ACC_SUPER
Constant pool: //常量池
//#1:方法引用: 引用了第6项和20项, Object类 init方法
//#2:成员变量引用:引用了21 和 22 项 out变量+Pri–类型
//#3: 字符串 : 对应Hello World!
//#4:方法引用,引用了24 和 25 项 Pri–类型+println方法
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // Hello World!
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // com/mycompany/HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/mycompany/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 Hello World!
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 com/mycompany/HelloWorld
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public com.mycompany.HelloWorld();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/HelloWorld;
//main方法的分析
//参数类型是字符串数组
//访问修饰符号:public static
//code:方法属性
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
//stack:最大操作数栈的深度
//locals:局部变量表的长度
//args_ize:参数长度
//aload_0:把局部变量的第0项加载到操作数栈
//0 3 5代表的是字节码的代码行号
stack=2, locals=1, args_size=1
//#2 代表引用常量池中的 第2项 #2
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
//line 5 : 0;5:源文件中的行号 对应 0:字节码中的行号
LineNumberTable:
line 5: 0
line 6: 8
//LocalVariableTable:本地变量表;变量的作用范围9行 0-8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
(三)class文件执行流程
Chapter 6. The Java Virtual Machine Instruction Set
java源文件
/**
* 字节码指令 和 操作数栈、常量池的关系
*/
public class ClassFile1 {
public static void main(String[] args) {
int a = 10;
int b = Short.MAX_VALUE + 1;
int c = a + b;
System.out.println(c);
}
}
执行 javap -v class文件名.class 反编译class文件
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile1.class
Last modified 2022-10-8; size 640 bytes
MD5 checksum e12dc813bd8300b8e972e26436bb6a34
Compiled from "ClassFile1.java"
public class com.mycompany.classfile.ClassFile1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#25 // java/lang/Object."<init>":()V
#2 = Class #26 // java/lang/Short
#3 = Integer 32768
#4 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #29.#30 // java/io/PrintStream.println:(I)V
#6 = Class #31 // com/mycompany/classfile/ClassFile1
#7 = Class #32 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/mycompany/classfile/ClassFile1;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 a
#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 SourceFile
#24 = Utf8 ClassFile1.java
#25 = NameAndType #8:#9 // "<init>":()V
#26 = Utf8 java/lang/Short
#27 = Class #33 // java/lang/System
#28 = NameAndType #34:#35 // out:Ljava/io/PrintStream;
#29 = Class #36 // java/io/PrintStream
#30 = NameAndType #37:#38 // println:(I)V
#31 = Utf8 com/mycompany/classfile/ClassFile1
#32 = Utf8 java/lang/Object
#33 = Utf8 java/lang/System
#34 = Utf8 out
#35 = Utf8 Ljava/io/PrintStream;
#36 = Utf8 java/io/PrintStream
#37 = Utf8 println
#38 = Utf8 (I)V
{
public com.mycompany.classfile.ClassFile1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: ldc #3 // int 32768
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 8: 0
line 9: 3
line 10: 6
line 11: 10
line 12: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 args [Ljava/lang/String;
3 15 1 a I
6 12 2 b I
10 8 3 c I
}
SourceFile: "ClassFile1.java"
1、常量池载入运行时常量池
2、方法字节码载入方法区
3、main 线程开始运行,分配栈帧内存
stack=2, locals=4
4、执行引擎开始执行字节码
(1)bipush 10
1、将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有sipush
2、sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
3、ldc 将一个 int 压入操作数栈
4、ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)
这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池
(2)istore_1
将操作数栈顶数据弹出,存入局部变量表的 slot 1
(3)ldc #3
从常量池加载 #3 数据到操作数栈
注:
Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好
(4)istore_2
(5)iload_1
(6)iload_2
(7)iadd
(8)istore_3
(9)getstatic #4
(10)iload_3
(11)invokevirtual #5
找到常量池 #5 项
定位到方法区 java/io/PrintStream.println:(I)V 方法
生成新的栈帧(分配 locals、stack等)
传递参数,执行新栈帧中的字节码
执行完毕,弹出栈帧
清除 main 操作数栈内容
(12)return
完成 main 方法调用,弹出 main 栈帧
程序结束
(四)条件判断指令
指令 | 助记符 | 含义 |
0x99 | ifeq | 判断是否 == 0 |
0x9a | ifne | 判断是否 != 0 |
0x9b | iflt | 判断是否 < 0 |
0x9c | ifge | 判断是否 >= 0 |
0x9d | ifgt | 判断是否 > 0 |
0x9e | ifle | 判断是否 <= 0 |
0x9f | if_icmpeq | 两个int是否 == |
0xa0 | if_icmpne | 两个int是否 != |
0xa1 | if_icmplt | 两个int是否 < |
0xa2 | if_icmpge | 两个int是否 >= |
0xa3 | if_icmpgt | 两个int是否 > |
0xa4 | if_icmple | 两个int是否 <= |
0xa5 | if_acmpeq | 两个引用是否 == |
0xa6 | if_acmpne | 两个引用是否 != |
0xc6 | ifnull | 判断是否 == null |
0xc7 | ifnonnull | 判断是否 != null |
byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节
goto 用来进行跳转到指定行号的字节码
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile3.class
Last modified 2022-10-8; size 499 bytes
MD5 checksum d1fc4d3f4e62927827257e4746785ca6
Compiled from "ClassFile3.java"
public class com.mycompany.classfile.ClassFile3
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#20 // java/lang/Object."<init>":()V
#2 = Class #21 // com/mycompany/classfile/ClassFile3
#3 = Class #22 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/mycompany/classfile/ClassFile3;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 a
#16 = Utf8 I
#17 = Utf8 StackMapTable
#18 = Utf8 SourceFile
#19 = Utf8 ClassFile3.java
#20 = NameAndType #4:#5 // "<init>":()V
#21 = Utf8 com/mycompany/classfile/ClassFile3
#22 = Utf8 java/lang/Object
{
public com.mycompany.classfile.ClassFile3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile3;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ifne 12
6: bipush 10
8: istore_1
9: goto 15
12: bipush 20
14: istore_1
15: return
LineNumberTable:
line 8: 0
line 9: 2
line 10: 6
line 12: 12
line 14: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
2 14 1 a I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 12
locals = [ int ]
frame_type = 2 /* same */
}
SourceFile: "ClassFile3.java"
(五)循环控制指令
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile4.class
Last modified 2022-10-8; size 494 bytes
MD5 checksum 7699a41df768a57866579ec358e6363e
Compiled from "ClassFile4.java"
public class com.mycompany.classfile.ClassFile4
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#20 // java/lang/Object."<init>":()V
#2 = Class #21 // com/mycompany/classfile/ClassFile4
#3 = Class #22 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/mycompany/classfile/ClassFile4;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 a
#16 = Utf8 I
#17 = Utf8 StackMapTable
#18 = Utf8 SourceFile
#19 = Utf8 ClassFile4.java
#20 = NameAndType #4:#5 // "<init>":()V
#21 = Utf8 com/mycompany/classfile/ClassFile4
#22 = Utf8 java/lang/Object
{
public com.mycompany.classfile.ClassFile4();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile4;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return
LineNumberTable:
line 8: 0
line 9: 2
line 10: 8
line 12: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 args [Ljava/lang/String;
2 13 1 a I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 11 /* same */
}
SourceFile: "ClassFile4.java"
(六)构造方法
1、<cinit>()V
public class ClassFile7 {
static int i = 20;
public ClassFile7() {
}
public static void main(String[] args) {
System.out.println(i);
}
static {
i = 30;
i = 10;
}
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile7.class
Last modified 2022-10-8; size 647 bytes
MD5 checksum 7db17d47259672d9fa7018e8ae0488d7
Compiled from "ClassFile7.java"
public class com.mycompany.classfile.ClassFile7
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Fieldref #5.#26 // com/mycompany/classfile/ClassFile7.i:I
#4 = Methodref #27.#28 // java/io/PrintStream.println:(I)V
#5 = Class #29 // com/mycompany/classfile/ClassFile7
#6 = Class #30 // java/lang/Object
#7 = Utf8 i
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/mycompany/classfile/ClassFile7;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 <clinit>
#21 = Utf8 SourceFile
#22 = Utf8 ClassFile7.java
#23 = NameAndType #9:#10 // "<init>":()V
#24 = Class #31 // java/lang/System
#25 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
#26 = NameAndType #7:#8 // i:I
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(I)V
#29 = Utf8 com/mycompany/classfile/ClassFile7
#30 = Utf8 java/lang/Object
#31 = Utf8 java/lang/System
#32 = Utf8 out
#33 = Utf8 Ljava/io/PrintStream;
#34 = Utf8 java/io/PrintStream
#35 = Utf8 println
#36 = Utf8 (I)V
{
static int i;
descriptor: I
flags: ACC_STATIC
public com.mycompany.classfile.ClassFile7();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile7;
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 #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field i:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: return
LineNumberTable:
line 18: 0
line 19: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 20
2: putstatic #3 // Field i:I
5: bipush 30
7: putstatic #3 // Field i:I
10: bipush 10
12: putstatic #3 // Field i:I
15: return
LineNumberTable:
line 8: 0
line 12: 5
line 15: 10
}
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V
<cinit>()V 方法会在类加载的初始化阶段被调用
2、<init>()V
public class ClassFile8 {
private String a = "s1";
{
b = 20;
}
private int b = 10;
{
a = "s2";
}
public ClassFile8(String a, int b) {
this.a = a;
this.b = b;
}
public static void main(String[] args) {
ClassFile8 d = new ClassFile8("s3", 30);
System.out.println(d.a);
System.out.println(d.b);
}
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile8.class
Last modified 2022-10-8; size 837 bytes
MD5 checksum 8e7d6afa5949cdbee15d459a0ff1e708
Compiled from "ClassFile8.java"
public class com.mycompany.classfile.ClassFile8
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #12.#31 // java/lang/Object."<init>":()V
#2 = String #32 // s1
#3 = Fieldref #6.#33 // com/mycompany/classfile/ClassFile8.a:Ljava/lang/String;
#4 = Fieldref #6.#34 // com/mycompany/classfile/ClassFile8.b:I
#5 = String #35 // s2
#6 = Class #36 // com/mycompany/classfile/ClassFile8
#7 = String #37 // s3
#8 = Methodref #6.#38 // com/mycompany/classfile/ClassFile8."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #41.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Methodref #41.#43 // java/io/PrintStream.println:(I)V
#12 = Class #44 // java/lang/Object
#13 = Utf8 a
#14 = Utf8 Ljava/lang/String;
#15 = Utf8 b
#16 = Utf8 I
#17 = Utf8 <init>
#18 = Utf8 (Ljava/lang/String;I)V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 Lcom/mycompany/classfile/ClassFile8;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 d
#29 = Utf8 SourceFile
#30 = Utf8 ClassFile8.java
#31 = NameAndType #17:#45 // "<init>":()V
#32 = Utf8 s1
#33 = NameAndType #13:#14 // a:Ljava/lang/String;
#34 = NameAndType #15:#16 // b:I
#35 = Utf8 s2
#36 = Utf8 com/mycompany/classfile/ClassFile8
#37 = Utf8 s3
#38 = NameAndType #17:#18 // "<init>":(Ljava/lang/String;I)V
#39 = Class #46 // java/lang/System
#40 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#41 = Class #49 // java/io/PrintStream
#42 = NameAndType #50:#51 // println:(Ljava/lang/String;)V
#43 = NameAndType #50:#52 // println:(I)V
#44 = Utf8 java/lang/Object
#45 = Utf8 ()V
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/io/PrintStream
#50 = Utf8 println
#51 = Utf8 (Ljava/lang/String;)V
#52 = Utf8 (I)V
{
public com.mycompany.classfile.ClassFile8(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String s1
7: putfield #3 // Field a:Ljava/lang/String;
10: aload_0
11: bipush 20
13: putfield #4 // Field b:I
16: aload_0
17: bipush 10
19: putfield #4 // Field b:I
22: aload_0
23: ldc #5 // String s2
25: putfield #3 // Field a:Ljava/lang/String;
28: aload_0
29: aload_1
30: putfield #3 // Field a:Ljava/lang/String;
33: aload_0
34: iload_2
35: putfield #4 // Field b:I
38: return
LineNumberTable:
line 19: 0
line 7: 4
line 10: 10
line 13: 16
line 16: 22
line 20: 28
line 21: 33
line 22: 38
LocalVariableTable:
Start Length Slot Name Signature
0 39 0 this Lcom/mycompany/classfile/ClassFile8;
0 39 1 a Ljava/lang/String;
0 39 2 b I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: new #6 // class com/mycompany/classfile/ClassFile8
3: dup
4: ldc #7 // String s3
6: bipush 30
8: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
11: astore_1
12: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
15: aload_1
16: getfield #3 // Field a:Ljava/lang/String;
19: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_1
26: getfield #4 // Field b:I
29: invokevirtual #11 // Method java/io/PrintStream.println:(I)V
32: return
LineNumberTable:
line 25: 0
line 26: 12
line 27: 22
line 28: 32
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 args [Ljava/lang/String;
12 21 1 d Lcom/mycompany/classfile/ClassFile8;
}
SourceFile: "ClassFile8.java"
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构 造方法内的代码总是在最后
(七)方法调用
public class ClassFile9 {
public ClassFile9() { }
private void test1() { }
private final void test2() { }
public void test3() { }
public static void test4() { }
@Override
public String toString() {
return super.toString();
}
public static void main(String[] args) {
ClassFile9 d = new ClassFile9();
d.test1();
d.test2();
d.test3();
d.test4();
ClassFile9.test4();
d.toString();
}
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile9.class
Last modified 2022-10-8; size 893 bytes
MD5 checksum ff6e5ab3e67ae93660e40cb67828777d
Compiled from "ClassFile9.java"
public class com.mycompany.classfile.ClassFile9
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#31 // java/lang/Object."<init>":()V
#2 = Methodref #10.#32 // java/lang/Object.toString:()Ljava/lang/String;
#3 = Class #33 // com/mycompany/classfile/ClassFile9
#4 = Methodref #3.#31 // com/mycompany/classfile/ClassFile9."<init>":()V
#5 = Methodref #3.#34 // com/mycompany/classfile/ClassFile9.test1:()V
#6 = Methodref #3.#35 // com/mycompany/classfile/ClassFile9.test2:()V
#7 = Methodref #3.#36 // com/mycompany/classfile/ClassFile9.test3:()V
#8 = Methodref #3.#37 // com/mycompany/classfile/ClassFile9.test4:()V
#9 = Methodref #3.#32 // com/mycompany/classfile/ClassFile9.toString:()Ljava/lang/String;
#10 = Class #38 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/mycompany/classfile/ClassFile9;
#18 = Utf8 test1
#19 = Utf8 test2
#20 = Utf8 test3
#21 = Utf8 test4
#22 = Utf8 toString
#23 = Utf8 ()Ljava/lang/String;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 d
#29 = Utf8 SourceFile
#30 = Utf8 ClassFile9.java
#31 = NameAndType #11:#12 // "<init>":()V
#32 = NameAndType #22:#23 // toString:()Ljava/lang/String;
#33 = Utf8 com/mycompany/classfile/ClassFile9
#34 = NameAndType #18:#12 // test1:()V
#35 = NameAndType #19:#12 // test2:()V
#36 = NameAndType #20:#12 // test3:()V
#37 = NameAndType #21:#12 // test4:()V
#38 = Utf8 java/lang/Object
{
public com.mycompany.classfile.ClassFile9();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile9;
public void test3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/mycompany/classfile/ClassFile9;
public static void test4();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 15: 0
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #2 // Method java/lang/Object.toString:()Ljava/lang/String;
4: areturn
LineNumberTable:
line 19: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile9;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #3 // class com/mycompany/classfile/ClassFile9
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokespecial #5 // Method test1:()V
12: aload_1
13: invokespecial #6 // Method test2:()V
16: aload_1
17: invokevirtual #7 // Method test3:()V
20: aload_1
21: pop
22: invokestatic #8 // Method test4:()V
25: invokestatic #8 // Method test4:()V
28: aload_1
29: invokevirtual #9 // Method toString:()Ljava/lang/String;
32: pop
33: return
LineNumberTable:
line 23: 0
line 24: 8
line 25: 12
line 26: 16
line 27: 20
line 28: 25
line 29: 28
line 30: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
8 26 1 d Lcom/mycompany/classfile/ClassFile9;
}
SourceFile: "ClassFile9.java"
1、new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
2、dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配 合 invokespecial 调用该对象的构造方法 "":()V (会消耗掉栈顶一个引用),另一个要 配合 astore_1 赋值给局部变量
3、最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静 态绑定
4、普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
5、成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
6、d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用 invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了
7、还有一个执行 invokespecial 的情况是通过 super 调用父类方法
(八)多态原理
/**
* 多态原理,JVM 参数,禁用指针压缩
* -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
*/
public class ClassFile10 {
public static void test(Animal animal) {
animal.eat();
System.out.println(animal.toString());
}
public static void main(String[] args) throws IOException {
test(new Cat());
test(new Dog());
System.in.read();
}
}
abstract class Animal {
public abstract void eat();
@Override
public String toString() {
return "我是" + this.getClass().getSimpleName();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("啃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("吃鱼");
}
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile10.class
Last modified 2022-10-8; size 1079 bytes
MD5 checksum 6bedd141f28a01d372823cac5f67f63a
Compiled from "ClassFile10.java"
public class com.mycompany.classfile.ClassFile10
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #14.#34 // java/lang/Object."<init>":()V
#2 = Methodref #35.#36 // com/mycompany/classfile/Animal.eat:()V
#3 = Fieldref #37.#38 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #35.#39 // com/mycompany/classfile/Animal.toString:()Ljava/lang/String;
#5 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #42 // com/mycompany/classfile/Cat
#7 = Methodref #6.#34 // com/mycompany/classfile/Cat."<init>":()V
#8 = Methodref #13.#43 // com/mycompany/classfile/ClassFile10.test:(Lcom/mycompany/classfile/Animal;)V
#9 = Class #44 // com/mycompany/classfile/Dog
#10 = Methodref #9.#34 // com/mycompany/classfile/Dog."<init>":()V
#11 = Fieldref #37.#45 // java/lang/System.in:Ljava/io/InputStream;
#12 = Methodref #46.#47 // java/io/InputStream.read:()I
#13 = Class #48 // com/mycompany/classfile/ClassFile10
#14 = Class #49 // java/lang/Object
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 Lcom/mycompany/classfile/ClassFile10;
#22 = Utf8 test
#23 = Utf8 (Lcom/mycompany/classfile/Animal;)V
#24 = Utf8 animal
#25 = Utf8 Lcom/mycompany/classfile/Animal;
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 args
#29 = Utf8 [Ljava/lang/String;
#30 = Utf8 Exceptions
#31 = Class #50 // java/io/IOException
#32 = Utf8 SourceFile
#33 = Utf8 ClassFile10.java
#34 = NameAndType #15:#16 // "<init>":()V
#35 = Class #51 // com/mycompany/classfile/Animal
#36 = NameAndType #52:#16 // eat:()V
#37 = Class #53 // java/lang/System
#38 = NameAndType #54:#55 // out:Ljava/io/PrintStream;
#39 = NameAndType #56:#57 // toString:()Ljava/lang/String;
#40 = Class #58 // java/io/PrintStream
#41 = NameAndType #59:#60 // println:(Ljava/lang/String;)V
#42 = Utf8 com/mycompany/classfile/Cat
#43 = NameAndType #22:#23 // test:(Lcom/mycompany/classfile/Animal;)V
#44 = Utf8 com/mycompany/classfile/Dog
#45 = NameAndType #61:#62 // in:Ljava/io/InputStream;
#46 = Class #63 // java/io/InputStream
#47 = NameAndType #64:#65 // read:()I
#48 = Utf8 com/mycompany/classfile/ClassFile10
#49 = Utf8 java/lang/Object
#50 = Utf8 java/io/IOException
#51 = Utf8 com/mycompany/classfile/Animal
#52 = Utf8 eat
#53 = Utf8 java/lang/System
#54 = Utf8 out
#55 = Utf8 Ljava/io/PrintStream;
#56 = Utf8 toString
#57 = Utf8 ()Ljava/lang/String;
#58 = Utf8 java/io/PrintStream
#59 = Utf8 println
#60 = Utf8 (Ljava/lang/String;)V
#61 = Utf8 in
#62 = Utf8 Ljava/io/InputStream;
#63 = Utf8 java/io/InputStream
#64 = Utf8 read
#65 = Utf8 ()I
{
public com.mycompany.classfile.ClassFile10();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mycompany/classfile/ClassFile10;
public static void test(com.mycompany.classfile.Animal);
descriptor: (Lcom/mycompany/classfile/Animal;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method com/mycompany/classfile/Animal.eat:()V
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_0
8: invokevirtual #4 // Method com/mycompany/classfile/Animal.toString:()Ljava/lang/String;
11: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: return
LineNumberTable:
line 11: 0
line 12: 4
line 13: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 animal Lcom/mycompany/classfile/Animal;
public static void main(java.lang.String[]) throws java.io.IOException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #6 // class com/mycompany/classfile/Cat
3: dup
4: invokespecial #7 // Method com/mycompany/classfile/Cat."<init>":()V
7: invokestatic #8 // Method test:(Lcom/mycompany/classfile/Animal;)V
10: new #9 // class com/mycompany/classfile/Dog
13: dup
14: invokespecial #10 // Method com/mycompany/classfile/Dog."<init>":()V
17: invokestatic #8 // Method test:(Lcom/mycompany/classfile/Animal;)V
20: getstatic #11 // Field java/lang/System.in:Ljava/io/InputStream;
23: invokevirtual #12 // Method java/io/InputStream.read:()I
26: pop
27: return
LineNumberTable:
line 16: 0
line 17: 10
line 18: 20
line 19: 27
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 args [Ljava/lang/String;
Exceptions:
throws java.io.IOException
}
SourceFile: "ClassFile10.java"
1、运行代码
停在 System.in.read() 方法上,这时运行 jps 获取进程 id
jps
20928 Jps
19348 ClassFile10
3412 Launcher
2296 RemoteMavenServer36
3084
2、运行 HSDB 工具
C:
// 进入 JDK 安装目录,执行
C:\>cd C:\Users\zhangm\.jdks\corretto-1.8.0_342
java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
进入图形界面 attach 进程 id
输入进程号 即可
如果遇到报错 Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: D:\JDK\jre1.8.0_291\bin\sawindbg.dll
Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: D:\JDK\jre1.8.0_291\bin\sawindbg.dll
将 D:\JDK\jdk1.8.0_291\jre\bin 目录下 sawindbg.dll 粘贴到 D:\JDK\jre1.8.0_291\bin 目录下 即可
3、查找对象
打开 Tools -> Find Object By Query
输入 select d from com.mycompany.classfile.Dog d 点击 Execute 执行
4、查看对象内存结构
点击超链接可以看到对象的内存结构,此对象没有任何属性,因此只有对象头的 16 字节,前 8 字节是 MarkWord,后 8 字节就是对象的 Class 指针
目前看不到它的实际地址
5、查看对象 Class 的内存地址
以通过 Windows -> Console 进入命令行模式,执行
mem 0x0000000741909d20 2
mem 有两个参数,参数 1 是对象地址,参数 2 是查看 2 行(即 16 字节)
结果中第二行 0x00000000f800c444 即为 Class 的内存地址
6、查看类的 vtable
方法一:Alt+R 进入 Inspector 工具,输入刚才的 Class 内存地址,看到如下界面
方法二:Tools -> Class Browser 输入 Dog 查找
Dog Class 的 vtable 长度为 6,意思就是 Dog 类有 6 个虚方法(多态 相关的,final,static 不会列入)
从 Class 的起始地址开始算,偏移 0x1b8 就是 vtable 的起始地址
7、验证方法地址
通过 Tools -> Class Browser 查看每个类的方法定义
8、当执行 invokevirtual 指令时
(1)先通过栈帧中的对象引用找到对象
(2)分析对象头,找到对象的实际 Class
(3)Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
(4)查表得到方法的具体地址
(5)执行方法的字节码
(九)异常处理
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 12
8: astore_2
9: bipush 20
11: istore_1
12: return
Exception table:
from to target type
2 5 8 Class java/lang/Exception
LineNumberTable:
line 5: 0
line 7: 2
line 10: 5
line 8: 8
line 9: 9
line 11: 12
LocalVariableTable:
Start Length Slot Name Signature
9 3 2 e Ljava/lang/Exception;
0 13 0 args [Ljava/lang/String;
2 11 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
}
多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围 内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置
1、多个 single-catch 块
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (ArithmeticException e) {
i = 30;
} catch (NullPointerException e) {
i = 40;
} catch (Exception e) {
i = 50;
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 26
8: astore_2
9: bipush 30
11: istore_1
12: goto 26
15: astore_2
16: bipush 40
18: istore_1
19: goto 26
22: astore_2
23: bipush 50
25: istore_1
26: return
Exception table:
from to target type
2 5 8 Class java/lang/ArithmeticException
2 5 15 Class java/lang/NullPointerException
2 5 22 Class java/lang/Exception
LineNumberTable:
line 5: 0
line 7: 2
line 14: 5
line 8: 8
line 9: 9
line 14: 12
line 10: 15
line 11: 16
line 14: 19
line 12: 22
line 13: 23
line 15: 26
LocalVariableTable:
Start Length Slot Name Signature
9 3 2 e Ljava/lang/ArithmeticException;
16 3 2 e Ljava/lang/NullPointerException;
23 3 2 e Ljava/lang/Exception;
0 27 0 args [Ljava/lang/String;
2 25 1 i I
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/ArithmeticException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/NullPointerException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
2、multi-catch 的情况
public static void main(String[] args) {
try {
Method test = ClassFile13.class.getMethod("test");
test.invoke(null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public static void test() {
System.out.println("ok");
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: ldc #2 // class com/mycompany/classfile/ClassFile13
2: ldc #3 // String test
4: iconst_0
5: anewarray #4 // class java/lang/Class
8: invokevirtual #5 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
11: astore_1
12: aload_1
13: aconst_null
14: iconst_0
15: anewarray #6 // class java/lang/Object
18: invokevirtual #7 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
21: pop
22: goto 30
25: astore_1
26: aload_1
27: invokevirtual #11 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
30: return
Exception table:
from to target type
0 22 25 Class java/lang/NoSuchMethodException
0 22 25 Class java/lang/IllegalAccessException
0 22 25 Class java/lang/reflect/InvocationTargetException
LineNumberTable:
line 9: 0
line 10: 12
line 13: 22
line 11: 25
line 12: 26
line 14: 30
LocalVariableTable:
Start Length Slot Name Signature
12 10 1 test Ljava/lang/reflect/Method;
26 4 1 e Ljava/lang/ReflectiveOperationException;
0 31 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 89 /* same_locals_1_stack_item */
stack = [ class java/lang/ReflectiveOperationException ]
frame_type = 4 /* same */
public static void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String ok
5: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 17: 0
line 18: 8
3、finally
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1 // 0 -> i
2: bipush 10 // try --------------------------------------
4: istore_1 // 10 -> i |
5: bipush 30 // finally |
7: istore_1 // 30 -> i |
8: goto 27 // return -----------------------------------
11: astore_2 // catch Exceptin -> e ----------------------
12: bipush 20 // |
14: istore_1 // 20 -> i |
15: bipush 30 // finally |
17: istore_1 // 30 -> i |
18: goto 27 // return -----------------------------------
21: astore_3 // catch any -> slot 3 ----------------------
22: bipush 30 // finally |
24: istore_1 // 30 -> i |
25: aload_3 // <- slot 3 |
26: athrow // throw ------------------------------------
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any // 剩余的异常类型,比如 Error
11 15 21 any // 剩余的异常类型,比如 Error
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
12 3 2 e Ljava/lang/Exception;
0 28 0 args [Ljava/lang/String;
2 26 1 i I
StackMapTable: ...
MethodParameters: ...
finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流 程
(十)synchronized
public static void main(String[] args) {
Object lock = new Object();
synchronized (lock) {
System.out.println("ok");
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // new Object
3: dup
4: invokespecial #1 // invokespecial <init>:()V
7: astore_1 // lock引用 -> lock
8: aload_1 // <- lock (synchronized开始)
9: dup
10: astore_2 // lock引用 -> slot 2
11: monitorenter // monitorenter(lock引用)
12: getstatic #3 // <- System.out
15: ldc #4 // <- "ok"
17: invokevirtual #5 // invokevirtual println:
(Ljava/lang/String;)V
20: aload_2 // <- slot 2(lock引用)
21: monitorexit // monitorexit(lock引用)
22: goto 30
25: astore_3 // any -> slot 3
26: aload_2 // <- slot 2(lock引用)
27: monitorexit // monitorexit(lock引用)
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: ...
MethodParameters: ...
注:
方法级别的 synchronized 不会在字节码指令中有所体现