深入理解java虚拟机第三版读书笔记06中介绍了class文件结构,这里我们动手实践,编译一个类查看一下它的字节码。
java源码:
public class Main {
private int i = 10;
private static int j = 40000;
public static String str = "Hello World!";
public static void main(String[] args){
System.out.println(str);
}
}
这个类有方法有实例变量有静态变量,在JDK8环境下编译后的字节码开头部分如下图:

我们来逐字节分析:
魔数、次版本号、主版本号
00~03:CA FE BA BE
,魔数。
04\~05:00 00
Minor Version,次版本号
06\~07:00 34
十进制52,Major Version,主版本号,两者对应JDK8
常量池
08~09:00 2F
十进制47,代表常量池内项的数量。
10开始为常量池中的各表:
#1
0A:0A
十进制10,代表CONSTANT_Methodref_info
类型,
0B~0E:00 0A 00 1E
#10,#30
#2
0F:09
十进制9,代表CONSTANT_Fieldref_info
类型,
10~13:00 09 00 1F
#9,#31
#3
14:09
十进制9,代表CONSTANT_Fieldref_info
类型,
15~18:00 20 00 21
#32,#33
#4
19:09
十进制9,代表CONSTANT_Fieldref_info
类型,
1A~1D:00 09 00 22
#9,#34
#5
1E:0A
十进制9,代表CONSTANT_Methodref_info
类型,
1F~22:00 23 00 24
#35,#36
#6
1E:03
十进制3,代表CONSTANT_Integer_info
类型,
15~18:00 00 9C 40
40000
#7
28:09
十进制9,代表CONSTANT_Fieldref_info
类型,
29~2C:00 09 00 25
#9,#37
#8
2D:08
十进制8,代表CONSTANT_String_info
类型,
2E~2F:00 26
#38
#9
30:07
十进制7,代表CONSTANT_Class_info
类型,
31~32:00 27
#39
#10
33:07
十进制7,代表CONSTANT_Class_info
类型,
34~35:00 28
#40
#11
36:01
十进制1,代表CONSTANT_Utf8_info
类型,
37~38:00 01
代表长度为1,39:69
代表字符'i'
#12
3A:01
十进制1,代表CONSTANT_Utf8_info
类型,
3B~3C:00 01
代表长度为1,3D:49
代表字符'I'
剩余常量池的项我们用javap得到(其实累了):
#13 = Utf8 j
#14 = Utf8 str
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 LocalVariableTable
#21 = Utf8 this
#22 = Utf8 LMain;
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 args
#26 = Utf8 [Ljava/lang/String;
#27 = Utf8 <clinit>
#28 = Utf8 SourceFile
#29 = Utf8 Main.java
#30 = NameAndType #16:#17 // "<init>":()V
#31 = NameAndType #11:#12 // i:I
#32 = Class #41 // java/lang/System
#33 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#34 = NameAndType #14:#15 // str:Ljava/lang/String;
#35 = Class #44 // java/io/PrintStream
#36 = NameAndType #45:#46 // println:(Ljava/lang/String;)V
#37 = NameAndType #13:#12 // j:I
#38 = Utf8 Hello World!
#39 = Utf8 Main
#40 = Utf8 java/lang/Object
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 java/io/PrintStream
#45 = Utf8 println
#46 = Utf8 (Ljava/lang/String;)V
访问标志、类索引、父类索引、接口索引集合
跳过常量池,到了访问标志:
01C1~01C2:00 21
,代表ACC_SUPER
(0x0020)和ACC_PUBLIC
(0x0001)。
接着是类索引:
01C3~01C4:00 09
代表常量池中#9,#9又指向#39,可以得知是Main,即类名
父类索引:
01C5~01C6:00 0A
代表常量池中#10,#10又指向#40,可以得知是java/lang/Object
接口索引集合:
01C7~01D8:00 00
代表接口索引集合中没有数据,长度是0
字段表集合
01C9~01CA: 00 03
十进制3,代表字段表中有三项数据:
第一个字段:
01CB\~01CC: 00 02
,访问标志,代表ACC_PRIVATE
(0x0002)
01CD\~01CE:00 0B
,name_index,指向常量池#11,#11代表'i'
01CF\~01D0:00 0C
, discriptor_index,指向常量池#12,#12代表'I'
(即int类型)
01D1\~01D2:00 00
, attributes_count,代表该字段无属性表。
第二个字段:
01D3\~01D4: 00 0A
,访问标志,代表ACC_STATIC
(0x0008)和ACC_PRIVATE
(0x0002)
01D5\~01D6:00 0D
,name_index,指向常量池#13,#13代表'j'
01D7\~01D8:00 0C
, discriptor_index,指向常量池#12,#12代表'I'
(即int类型)
01D9\~01DA:00 00
, attributes_count,代表该字段无属性表。
第三个字段:
01DB\~01DC: 00 09
,访问标志,代表ACC_STATIC
(0x0008)和ACC_PUBLIC
(0x0001)
01DD\~01DE:00 0E
,name_index,指向常量池#14,#14代表"str"
01DF\~01E0:00 0F
, discriptor_index,指向常量池#15,#15代表"Ljava/lang/String;"
(即String类型)
01E1\~01E2:00 00
, attributes_count,代表该字段无属性表。
方法表集合
01E3~01E4:00 03
,十进制3,代表方法表中有三项数据:
第一个方法:
01E5\~01E6: 00 01
,访问标志,代表ACC_PUBLIC
(0x0001)
01E7\~01E8:00 10
,name_index,指向常量池#16,#16代表"<init>"
(即对象构造器)
01E9\~01EA:00 11
, discriptor_index,指向常量池#17,#17代表"()V"
(即无参、无返回值)
01EB\~01EC:00 01
, attributes_count,代表属性表中有一项数据。
01ED\~01EE: 00 12
,attribute_name_index,指向常量池#18,#18代表"Code"
(即Code属性)
01EF\~01F2:00 00 00 39
,代表Code内容长度为57个字节。
接下来57个字节我们不查表逐一翻译,查看javap提供的内容:
01F3~022B
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 10
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 7: 0
line 8: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LMain;
我们来解释一下它的字节码指令:
aload_0
将局部变量表slot 0加载到操作数栈,那么局部变量表slot 0原来存放的是什么呢?非静态方法局部变量表0位置一开始都是存放的this,即调用方法的当前对象。这句话就是把this入操作数栈。
invokespecial #1
this调用#1代表的方法,我们查常量表#1,#1又指向#10和#30,#10指向#40,是java/lang/Object
,#30是"<init>":()V
,即调用父类Object的构造方法。
aload_0
再次加载this
bipush 10
将常量10压入操作数栈。
putfield #2
putfield是设置对象的字段值,通过查常量表,#2代表Main
中的i:I
,这句话就把栈里的两个操作数:10
设置给this.i
return
返回
第二个方法:
022C\~022D: 00 09
,访问标志,代表ACC_PUBLIC
(0x0001)和ACC_STATIC
(0x0008)
022E\~022F:00 17
,name_index,指向常量池#23,#23代表"main"
0230\~0231:00 18
, discriptor_index,指向常量池#24,#24代表"([Ljava/lang/String;)V"
(即参数为String数组、无返回值)
0232\~0233:00 01
, attributes_count,代表属性表中有一项数据。
0234\~0235: 00 12
,attribute_name_index,指向常量池#18,#18代表"Code"
(即Code属性)
0236\~0239:00 00 00 38
,代表Code内容长度为56个字节。
接下来56个字节我们不查表逐一翻译,查看javap提供的内容:
023A~0271
Code:
stack=2, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #4 // Field str:Ljava/lang/String;
6: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
LineNumberTable:
line 12: 0
line 13: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
我们来解释一下它的字节码指令:
getstatic #3
获取一个类的静态字段,通过查常量表可知#3是java/lang/System
的out:Ljava/io/PrintStream;
即获取System.out
getstatic #4
获取一个类的静态字段,通过查常量表可知#4是Main
的str:Ljava/lang/String;
即获取str字段。
invokevirtual #5
调用#5方法,通过查常量表可知#5是java/io/PrintStream
的println:(Ljava/lang/String;)V
,即在操作栈的基础上调用System.out.println(str)
return
返回
第三个方法:
0272\~0273: 00 08
,访问标志,代表ACC_STATIC
(0x0008)
0274\~0275:00 1B
,name_index,指向常量池#27,#27代表"<clinit>"
(即类构造器)
0276\~0277:00 11
, discriptor_index,指向常量池#17,#17代表"()V"
(即无参无返回值)
0278\~0279:00 01
, attributes_count,代表属性表中有一项数据。
027A\~027B: 00 12
,attribute_name_index,指向常量池#18,#18代表"Code"
(即Code属性)
027C\~027F:00 00 00 27
,代表Code内容长度为39个字节。
接下来39个字节我们不查表逐一翻译,查看javap提供的内容:
0280~02A6
Code:
stack=1, locals=0, args_size=0
0: ldc #6 // int 40000
2: putstatic #7 // Field j:I
5: ldc #8 // String Hello World!
7: putstatic #4 // Field str:Ljava/lang/String;
10: return
LineNumberTable:
line 9: 0
line 10: 5
我们来解释一下它的字节码指令:
ldc #6
把一个常量#6加载到操作数栈,通过查常量表可知#6是40000。
putstatic #7
设置一个类的静态字段,通过查常量表可知#7是Main
的j:I
,即设置j的值为40000。(注意这里与第一个方法不同的是,设置小于等于short最大值的值的时候常数放在字节码中,而大于short最大值的常量放在常量表中)
ldc #8
把一个常量#8加载到操作数栈,通过查常量表可知#8是"Hello World!"
。
putstatic #4
设置一个类的静态字段,通过查常量表可知#4是Main
的str:Ljava/lang/String;
,即将str的值设置为"Hello World!"
。
return
返回
属性表
02A7\~02A8:00 01
attributes_count,代表属性表中有一项数据
02A9\~02AA:00 1C
attribute_name_index,指向常量池#28,#28代表"SourceFile"
(记录源文件名称)
02AB\~02AE: 00 00 00 02
代表属性内容长度为2个字节。
02AF\~02B0: 00 1D
代表属性的值,指向常量池#29,#29代表"Main.java"
到此,该class文件的字节码全部分析完

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/java%e5%ad%97%e8%8a%82%e7%a0%81%e5%ae%9e%e4%be%8b%e6%8e%a2%e7%a9%b6/