字节码指令常识

前言

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

查看字节码工具和方法

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

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

字节码指令结构


IDEA jclasslib 查看就是按照这个结构排布

指令说明

核心理念理解

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

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

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

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

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