# JVM 字节码指令与 javap
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
下面是一些官方文档地址:
# 查看一个 class 文件的字节码信息
CLass 文件格式官方文档 (opens new window) 再次强调:本节内容知识,要想明白最好通读下官方文档的说明,里面讲得很详细
package cn.mrcode.stady.monitor_tuning.chapter8;
public class Test1 {
public static void main(String[] args) {
int a = 2;
int b=3;
int c = a+b;
System.out.println(c);
}
}
2
3
4
5
6
7
8
9
10
11
使用 IDEA 开发的话,直接找到 class 目录(这个要看你用的是什么打包工具),笔者是 gradle
# 进入到该文件的 class 文件
cd /Users/mrcode/IdeaProjects/monitor-tuning/build/classes/java/main/cn/mrcode/stady/monitor_tuning/chapter8
#输出到指定文件
javap -v Test1.class > test1.txt
2
3
4
5
test1.txt 文件内容如下,里面就是对应的字节码内容
Classfile /Users/mrcode/IdeaProjects/monitor-tuning/build/classes/java/main/cn/mrcode/stady/monitor_tuning/chapter8/Test1.class
Last modified 2021-2-7; size 659 bytes
MD5 checksum 354fdbdbbc2263bb6c52bf320134a8d9
Compiled from "Test1.java"
// 类名与 JDK 版本号
public class cn.mrcode.stady.monitor_tuning.chapter8.Test1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER // 访问标识符
// 常量池
// 里面的引用都是符号引用,在运行时会转成直接引用
Constant pool:
// 方法引用,部分常量后面的注释是引用的内容,可以跳到具体的常量查看
// 也可以直接看后面的注释内容
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #27.#28 // java/io/PrintStream.println:(I)V
// 第 4 个常量是一个 Class 引用了 #29 这个常量
#4 = Class #29 // cn/mrcode/stady/monitor_tuning/chapter8/Test1
#5 = Class #30 // java/lang/Object
// 构造函数
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcn/mrcode/stady/monitor_tuning/chapter8/Test1;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 a
#18 = Utf8 I
#19 = Utf8 b
#20 = Utf8 c
#21 = Utf8 MethodParameters
#22 = Utf8 SourceFile
#23 = Utf8 Test1.java
#24 = NameAndType #6:#7 // "<init>":()V
#25 = Class #31 // java/lang/System
#26 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(I)V
// #29 常量,是一个 Utf8,即是一个字符串
#29 = Utf8 cn/mrcode/stady/monitor_tuning/chapter8/Test1
#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
{
public cn.mrcode.stady.monitor_tuning.chapter8.Test1();
descriptor: ()V // 构造函数,返回是 void
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 Lcn/mrcode/stady/monitor_tuning/chapter8/Test1;
// main 函数
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code: // code 里面的内容也就是我们在 psvm 里面编写的类容的字节码
// 操作栈的深度为 2
// 本地变量表最大长度(slot 为单位),64 位是 2,其他是 1,索引从 0 开始,
// 如果是非 static 方法,索引 0 代表 this,后面是入参
// 本地变量表在这个信息的最后面有也就是:LocalVariableTable
// 有 1 个参数
stack=2, locals=4, args_size=1
0: iconst_2 // 常量 2 压栈
1: istore_1 // 出栈保存到本地变量 1 里面
2: iconst_3 // 常量 3 压栈
3: istore_2 // 出栈保存到本地变量 2 里面
4: iload_1 // 局部变量 1 压栈
5: iload_2 // 局部变量 2 压栈
6: iadd // 栈顶两个元素相加,计算结果压栈
7: istore_3 // 出栈保存到局部变量 3 里面
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: return
// 行号表
// line 5: 对应文件里面的代码行号
// 0:代表的是 code 里面的前面的操作步骤
LineNumberTable:
line 5: 0
line 6: 2
line 7: 4
line 8: 8
line 9: 15
// 本地变量表
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
2 14 1 a I
4 12 2 b I
8 8 3 c I
MethodParameters:
Name Flags
args
}
SourceFile: "Test1.java"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 字段描述符
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 | 引用类型,是这个类的一个实例 |
S | short | signed short |
Z | boolean | true or false |
[ | reference | 数组实例 |
# 方法描述符
方法描述符:
Object m(int i, double d, Thread t) {...}
字节码中是这样的:
(IDLjava/lang/Thread;)Ljava/lang/Object;
# IDL: 是方法的参数类型,也就是字段描述符对应的含义
2
需要注意的是的二进制名称的内部形式 Thread
和 Object
使用。
# 基于栈的架构
jvm 执行指令是基于栈的架构,还有听到最多的是基于寄存器的架构。
我们具体关注一下代码信息
public static void main(String[] args) {
int a = 2;
int b=3;
int c = a+b;
System.out.println(c);
}
2
3
4
5
6
以上代码对应的指令是下面这个
// int a = 2 对应下面两个指令操作
// 含义是:将常量 2 压入栈,然后出栈将值赋给本地变量 2
0: iconst_2 // 常量 2 压栈
1: istore_1 // 出栈保存到本地变量 1 里面
// int b = 3
2: iconst_3 // 常量 3 压栈
3: istore_2 // 出栈保存到本地变量 2 里面
// int c = a + b
4: iload_1 // 本地变量 1 压栈
5: iload_2 // 本地变量 2 压栈
6: iadd // 栈顶两个元素相加,计算结果压栈
7: istore_3 // 出栈保存到本地变量 3 里面
// System.out.println(c);
// 是一个 Fieldref 引用, 引用了常量池中的 #2,获取了一个静态方法
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3 // 本地变量 3 压栈,也就是计算结果
// 这里执行了 #3 引用,也就是 println 方法
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: return
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用图示表示如下:
将 2 赋值到本地变量表 1
将 3 赋值到本地变量表 2
将本地变量表 1、2 压栈
计算栈顶两个数值的值,留在栈中的一个元素则是计算后的结果,将该结果出栈,存储在本地变量表 3 中
获取了一个静态方法的引用,并将本地变量 3 压栈,形成一个引用和一个参数
执行方法,并返回结束该 main 函数
以上指令集的含义,请仔细阅读官方文档,这里以 invokevirtual (opens new window) 来举例,如何看懂这个文档
# Operation
Invoke instance method; dispatch based on class
调用实例方法;基于类的调度
# Format
invokevirtual indexbyte1 indexbyte2
# Forms
invokevirtual = 182 (0xb6)
# Operand Stack / 对操作数栈的要求
..., objectref, [arg1, [arg2 ...]] →
...
里面需要现有一个 objectref ,对应本例则是 getstatic
后面需要有该方法的参数,对应本例则是 iload_3
有了这两个,就可以执行 invokevirtual 指令了
对于官方的英文文档,如果看不懂的话,可以去百度该指令,英文对于指令来说,它的变化是不怎么频繁的