了解如何阅读字节码

前言

学会看字节码指令有助于理解JVM底层代码执行逻辑。本文做一些核心知识的小记方便自己查看。

查看字节码工具和方法

可以用javap查看字节码内容。使用IDE的插件的话可以用jclasslib和java decompile等插件

1
2
3
4
### 采用javap
javap -csvl Main.class

### 采用idea插件jclasslib 在IDEA中选中java文件然后使用show bytecode

字节码指令结构

image.png

javap命令说明

javap命令可以用来查阅字节码文件,可以将指定的字节码文件反编译,反解析出当前类对应基本信息、常量池(Constant pool)、字段区域、方法区域(Code[JVM指令集])、异常表(Exception table)、本地变量表(LocalVariableTable)、行数表(LineNumberTable)和字节码操作数栈的映射表(StackMapTable)等信息。

  • class文件的版本号:包括minor version和major version, major version是主版本号,55代表该Class文件利用JDK11编译,minor version为次版本号,低版本的class文件无法在新版本的JVM中运行。
  • flags代表访问标识:Class类文件的访问标识通常右ACC_开头,ACC_PUBLIC, ACC_SUPER代表这个类是public的,具体信息可以参考JVM规范。
  • Constant pool: 字节码的常量池是个静态常量池或者称之为类常量池,存放字面量和符号引用信息,按照索引组织
  • 字段区域:展示字段描述符、访问权限(flag)信息
  • 方法区域
    • 描述符和访问标志
    • stack=5,locals=3,args_size=3: 操作数栈、局部变量、参数的数量
    • Code区:展示具体的字节码指令以及其关联的符号引用
    • LineNumberTable:代码行数和指令索引之间的映射关系
    • Exception Table: from to 限定了try catch的代码行范围,type定义了捕捉的异常类型
    • LocalVariableTable: 局部变量表,start+length表示这个变量在字节码中的偏移位置
    • StackMapTable: 栈图,用于提升jvm类型检查的验证过程效率。涉及类验证的过程。StackMapTable主要用来验证跳转前后locals、stack中的类型和大小一致。

指令说明

核心理念理解

字节码指令比较多,具体不罗列了,可以看参考资料[1]。记忆这些指令主要理解JVM本身是基于堆栈的内存模型,数的存取与计算是通过局部变量表、常量池、操作数栈配合运算指令来完成。

下图展示JAVA虚拟机栈在类加载整个流程中的位置。图参考[3]

image.png

下图展示JAVA虚拟机栈在main执行后和栈帧的关系(下图绿色部分是栈帧的构成)

image.png

下图展示栈帧字节码指令之间的细节

栈帧中通过动态链接(constant pool reference)指向运行时常量池方法的直接应用(主要针对动态分派的对象,例如多态的方法)。关联的方法是inovkedynamic指令
image.png

tips: 栈操作指令都有个前缀,标识其服务的数据类型,例如i,si,l都分别可以代表integer,small int,long等

方法调用指令

看字节码指令的时候,方法调用指令还是会经常看到,这边罗列下其功能。

  • invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派), 这也是Java语言中最常见的方法分派方式。
  • invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
  • invokestatic指令:用于调用类静态方法(static方法)。
  • invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在Java虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑 是由用户所设定的引导方法决定的。

参考例子

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kaimingwan.questions;

/**
* @author wanshao create time is 2022/2/25
**/
public class ByteCodeShow {

public static void main(String[] args) {
int a = 5;
long b = 3;
long c = a + b;
String result = "Result is ";
System.out.println(result + c);
}

}

javap字节码查看

1
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
116
117
118
119
120
121
122
123
124
警告: 二进制文件ByteCodeShow包含com.kaimingwan.questions.ByteCodeShow
Classfile /Users/wanshao/projects/java-questions/target/classes/com/kaimingwan/questions/ByteCodeShow.class
Last modified 2022-2-25; size 923 bytes
MD5 checksum 8b5fdad13d0f32ec88a389856dca65c4
Compiled from "ByteCodeShow.java"
public class com.kaimingwan.questions.ByteCodeShow
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#34 // java/lang/Object."<init>":()V
#2 = Long 3l
#4 = String #35 // Result is
#5 = Fieldref #36.#37 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Class #38 // java/lang/StringBuilder
#7 = Methodref #6.#34 // java/lang/StringBuilder."<init>":()V
#8 = Methodref #6.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #6.#40 // java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
#10 = Methodref #6.#41 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Methodref #42.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V
#12 = Class #44 // com/kaimingwan/questions/ByteCodeShow
#13 = Class #45 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/kaimingwan/questions/ByteCodeShow;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 a
#26 = Utf8 I
#27 = Utf8 b
#28 = Utf8 J
#29 = Utf8 c
#30 = Utf8 result
#31 = Utf8 Ljava/lang/String;
#32 = Utf8 SourceFile
#33 = Utf8 ByteCodeShow.java
#34 = NameAndType #14:#15 // "<init>":()V
#35 = Utf8 Result is
#36 = Class #46 // java/lang/System
#37 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#38 = Utf8 java/lang/StringBuilder
#39 = NameAndType #49:#50 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#40 = NameAndType #49:#51 // append:(J)Ljava/lang/StringBuilder;
#41 = NameAndType #52:#53 // toString:()Ljava/lang/String;
#42 = Class #54 // java/io/PrintStream
#43 = NameAndType #55:#56 // println:(Ljava/lang/String;)V
#44 = Utf8 com/kaimingwan/questions/ByteCodeShow
#45 = Utf8 java/lang/Object
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 append
#50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = Utf8 (J)Ljava/lang/StringBuilder;
#52 = Utf8 toString
#53 = Utf8 ()Ljava/lang/String;
#54 = Utf8 java/io/PrintStream
#55 = Utf8 println
#56 = Utf8 (Ljava/lang/String;)V
{
public com.kaimingwan.questions.ByteCodeShow();
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/kaimingwan/questions/ByteCodeShow;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=7, args_size=1
0: iconst_5
1: istore_1
2: ldc2_w #2 // long 3l
5: lstore_2
6: iload_1
7: i2l
8: lload_2
9: ladd
10: lstore 4
12: ldc #4 // String Result is
14: astore 6
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: new #6 // class java/lang/StringBuilder
22: dup
23: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
26: aload 6
28: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: lload 4
33: invokevirtual #9 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
36: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
39: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
LineNumberTable:
line 9: 0
line 10: 2
line 11: 6
line 12: 12
line 13: 16
line 14: 42
LocalVariableTable:
Start Length Slot Name Signature
0 43 0 args [Ljava/lang/String;
2 41 1 a I
6 37 2 b J
12 31 4 c J
16 27 6 result Ljava/lang/String;
}
SourceFile: "ByteCodeShow.java"

参考资料

  1. 【JVM进阶之路】十二:字节码指令
  2. Jvm系列3—字节码指令
  3. 从栈帧看字节码是如何在 JVM 中进行流转的
  4. The Java Virtual Machine Instruction Set
  5. javap - 查阅 Java 字节码