Java字节码文件内容概览
- 魔数: 文件类型
- 主次版本号: jdk版本
- 常量池: Class文件的资源仓库
3.1. 常量池容量计数
3.2. 常量,主要是字面量和符号引用两类 - 访问标志: 声明是否接口, public还是private等
- 类索引: 类的全限定名
- 父类索引: 父类的全限定名
- 接口数量和接口索引: 接口数量和接口的全限定名, 如果接口数为0则接口索引不存在
- 字段数量和字段集合
- 方法数和方法集合
- Attributes
各种相关表格
常量池数据类型表
-
其中u1,u2,u4,u8等代表占用1,2,4,8字节
pool1.png
pool2.png
pool3.png
类访问标志
Access_Flags.png字段
-
字段表结构
field_table.png -
字段表访问标志
field_access_flags.png
方法
-
方法表结构
method_table.png -
方法表访问标志
method_access_flags.png
属性表
- Code属性表
- attribute_name_index: 固定执行 Code
- max_stack代表了操作数栈(Operand Stack)深度的最大值。
- max_locals代表了局部变量表所需的存储空间。
- code_length和code用来存储Java源程序编译后生成的字节码指令。
- exception_length和exception用来处理trycatch异常
- attribute: 属性
字节码文件分析
- 生成对应文件命令(Win PowerShell下)
# 需要先将java文件编译成class文件
# class字节码输出到class.txt中
javap -verbose .\HelloWord.class > class.txt
# class十六进制输出到hex.txt中
format-hex -path .\HelloWord.class > hex.txt
源码文件
- HelloWord.java
public class HelloWord {
private int c = 300;
public int d = 400;
public static void main(String[] args) {
int a = 100;
int b = 200;
System.out.println("hello word");
}
}
- HelloWord.class
public class com.example.demo.HelloWord
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #7 // com/example/demo/HelloWord
super_class: #8 // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #8.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #7.#29 // com/example/demo/HelloWord.c:I
#3 = Fieldref #7.#30 // com/example/demo/HelloWord.d:I
#4 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #33 // hello word
#6 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #36 // com/example/demo/HelloWord
#8 = Class #37 // java/lang/Object
#9 = Utf8 c
#10 = Utf8 I
#11 = Utf8 d
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/example/demo/HelloWord;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 a
#24 = Utf8 b
#25 = Utf8 MethodParameters
#26 = Utf8 SourceFile
#27 = Utf8 HelloWord.java
#28 = NameAndType #12:#13 // "<init>":()V
#29 = NameAndType #9:#10 // c:I
#30 = NameAndType #11:#10 // d:I
#31 = Class #38 // java/lang/System
#32 = NameAndType #39:#40 // out:Ljava/io/PrintStream;
#33 = Utf8 hello word
#34 = Class #41 // java/io/PrintStream
#35 = NameAndType #42:#43 // println:(Ljava/lang/String;)V
#36 = Utf8 com/example/demo/HelloWord
#37 = Utf8 java/lang/Object
#38 = Utf8 java/lang/System
#39 = Utf8 out
#40 = Utf8 Ljava/io/PrintStream;
#41 = Utf8 java/io/PrintStream
#42 = Utf8 println
#43 = Utf8 (Ljava/lang/String;)V
{
public int d;
descriptor: I
flags: (0x0001) ACC_PUBLIC
public com.example.demo.HelloWord();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: sipush 300
8: putfield #2 // Field c:I
11: aload_0
12: sipush 400
15: putfield #3 // Field d:I
18: return
LineNumberTable:
line 3: 0
line 5: 4
line 6: 11
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lcom/example/demo/HelloWord;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 100
2: istore_1
3: sipush 200
6: istore_2
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String hello word
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: return
LineNumberTable:
line 9: 0
line 10: 3
line 12: 7
line 13: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
3 13 1 a I
7 9 2 b I
MethodParameters:
Name Flags
args
}
SourceFile: "HelloWord.java"
- HelloWord十六进制
CA FE BA BE 00 00 00 34 00 2C 0A 00 08 00 1C 09
00 07 00 1D 09 00 07 00 1E 09 00 1F 00 20 08 00
21 0A 00 22 00 23 07 00 24 07 00 25 01 00 01 63
01 00 01 49 01 00 01 64 01 00 06 3C 69 6E 69 74
3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00
0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65
54 61 62 6C 65 01 00 04 74 68 69 73 01 00 1C 4C
63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 64 65 6D 6F
2F 48 65 6C 6C 6F 57 6F 72 64 3B 01 00 04 6D 61
69 6E 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E
67 2F 53 74 72 69 6E 67 3B 29 56 01 00 04 61 72
67 73 01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67
2F 53 74 72 69 6E 67 3B 01 00 01 61 01 00 01 62
01 00 10 4D 65 74 68 6F 64 50 61 72 61 6D 65 74
65 72 73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65
01 00 0E 48 65 6C 6C 6F 57 6F 72 64 2E 6A 61 76
61 0C 00 0C 00 0D 0C 00 09 00 0A 0C 00 0B 00 0A
07 00 26 0C 00 27 00 28 01 00 0A 68 65 6C 6C 6F
20 77 6F 72 64 07 00 29 0C 00 2A 00 2B 01 00 1A
63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 64 65 6D 6F
2F 48 65 6C 6C 6F 57 6F 72 64 01 00 10 6A 61 76
61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 10
6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69
6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 01 00
13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74
72 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00
15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72
69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
00 02 00 09 00 0A 00 00 00 01 00 0B 00 0A 00 00
00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
00 02 00 01 00 00 00 13 2A B7 00 01 2A 11 01 2C
B5 00 02 2A 11 01 90 B5 00 03 B1 00 00 00 02 00
0F 00 00 00 0E 00 03 00 00 00 03 00 04 00 05 00
0B 00 06 00 10 00 00 00 0C 00 01 00 00 00 13 00
11 00 12 00 00 00 09 00 13 00 14 00 02 00 0E 00
00 00 5A 00 02 00 03 00 00 00 10 10 64 3C 11 00
C8 3D B2 00 04 12 05 B6 00 06 B1 00 00 00 02 00
0F 00 00 00 12 00 04 00 00 00 09 00 03 00 0A 00
07 00 0C 00 0F 00 0D 00 10 00 00 00 20 00 03 00
00 00 10 00 15 00 16 00 00 00 03 00 0D 00 17 00
0A 00 01 00 07 00 09 00 18 00 0A 00 02 00 19 00
00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
00 1B
魔数和版本号
CA FE BA BE 00 00 00 34 00 2C 0A 00 08 00 1C 09
CA FE BA BE: 前四个字节表示文件类型,java
00 00: 此版本号: 0
00 34: 主版本号: 34转十进制是52,对应的jdk是1.8
常量池
00 2C 0A 00 08 00 1C 09
00 07 00 1D 09 00 07 00 1E 09 00 1F 00 20 08 00
21 0A 00 22 00 23 07 00 24 07 00 25 01 00 01 63
01 00 01 49 01 00 01 64 01 00 06 3C 69 6E 69 74
...
72 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00
15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72
69 6E 67 3B 29 56
00 2C: 常量池的数量,十进制是44,表示有43个常量,对应上面的字节码文件 Constant pool, 索引是1~44
0A: tag是十进制10,在常量池表中对应CONSTANT_MethodRef_info
00 08: MethodRef_info 的第一个index,指向 #8
00 1C: MethodRef_info 的第二个index,指向 #28
#1 = Methodref #8.#28 // java/lang/Object."<init>":()V
09: tag是09,CONSTANT_Fieldref_info
00 07: CONSTANT_Fieldref_info的第一个index,指向#7
00 1D: CONSTANT_Fieldref_info的第二个index,指向#29
#2 = Fieldref #7.#29 // com/example/demo/HelloWord.c:I
09: tag是09,CONSTANT_Fieldref_info
00 07: #7
00 1E: #30
#3 = Fieldref #7.#30 // com/example/demo/HelloWord.d:I
09: tag是09,CONSTANT_Fieldref_info
00 1F: #31
00 20: #32
#4 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
08: tag是08,CONSTANT_String_info
00 21: #33
#5 = String #33 // hello word
0A: tag是10,CONSTANT_MethodRef_info
00 22: #34
00 23: #35
#6 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
07: tag是7,CONSTANT_Class_info
00 24: #36
#7 = Class #36 // com/example/demo/HelloWord
07: tag是7,CONSTANT_Class_info
00 25: #37
#8 = Class #37 // java/lang/Object
01: tag是1,CONSTANT_Utf8_info
00 01: length为1
63: 读取一个byte,63 对应的 ASCII表 字符为 c
#9 = Utf8 c
01: tag是1,CONSTANT_Utf8_info
00 01: length为1
49: 读取一个byte,49 对应的 ASCII表 字符为 I,表示是int类型
#10 = Utf8 I
01: tag是1,CONSTANT_Utf8_info
00 01: length为1
64: 读取一个byte,64 对应的 ASCII表 字符为 d
#11 = Utf8 d
01: tag是1,CONSTANT_Utf8_info
00 06: length为6
3C 69 6E 69 74 3E: 读取6个byte,对应的 ASCII表 拼接为 <init>
#12 = Utf8 <init>
...以此类推
01: utf8
00
15: length 21 个byte
28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56: 拼接为 (Ljava/lang/String;)V
#43 = Utf8 (Ljava/lang/String;)V
访问标志和类全限定名,父类全限定名,接口集合等
69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
00 21: 访问标志位 0021 是0001和0020的并集,表示 ACC_PUBLIC 并 ACC_SUPER,表示的是父类 Object,java中所有类都有共同父类Object,是在编译时给你加上的
00 07: This Class 的全限定名,在常量池中查找 #7 得到 com/example/demo/HelloWord
00 08: 父类的全限定名,在常量池中查找 #8 得到 java/lang/Object
00 00: interface数量,0000表示接口数为0,如果不为0,那么会有interfaces接口表
字段
69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
00 02 00 09 00 0A 00 00 00 01 00 0B 00 0A 00 00
00 02: 字段表集合,表示有两个字段
00 02: field_access_flags,0002表示 ACC_PRIVATE
00 09: field_table name_index,#9 查询常量池得 c
00 0A: field_table descriptor_index, 描述符, 10,查询常量池得 I,int类型
00 00: field_table attributes_count,属性表,为0
00 01: field_access_flags,0002表示 ACC_PUBLIC
00 0B: field_table name_index,#11 查询常量池得 d
00 0A: field_table descriptor_index, 描述符, 10,查询常量池得 I,int类型
00 00: field_table attributes_count,属性表,为0
方法
00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
00 02: 方法数,两个,构造方法和main方法
第一个方法
00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
""方法属性""
00 01: method_access_flags, ACC_PUBLIC
00 0C: method_table name_index #12 <init>
00 0D: method_table descriptor_index #13 ()V 无参无返回值
00 01: attribute_count 1个属性
00 0E: 属性名 #14 Code
00 00 00 45 : 属性长度 69
""Code""
00 02 00 01 00 00 00 13 2A B7 00 01 2A 11 01 2C
B5 00 02 2A 11 01 90 B5 00 03 B1 00 00 00 02 00
00 02: max_stack 2
00 01: max_locals 1
00 00 00 13: code_length 19
""函数栈""
2A B7 00 01 2A 11 01 2C B5 00 02 2A 11 01 90 B5 00 03 B1: code代码,通过[虚拟机字节码指令](https://www.jianshu.com/p/e6d4e0f01228)查找具体释义
2A: 0 aload_0(将第一个引用变量推送到栈顶)
B7: 1 invokespecial(调用父类构造方法)
00: 空
01: aconst_null,将null推送到栈顶
2A: 4 aload_0(将第一个引用变量推送到栈顶)
11: 5 sipush 将一个short值带符号扩展成int推送至栈顶
01: aconst_null,将null推送到栈顶
2C: 将第3个引用类型本地变量推送至栈顶
B5: 8 putfield, 给对象字段赋值
00: 空
02: iconst_m1, 将-1推送到栈顶
2A: 11 aload_0(将第一个引用变量推送到栈顶)
11: 12 sipush 将一个short值带符号扩展成int推送至栈顶
01: aconst_null,将null推送到栈顶
90: d2f, (float) a, 将栈顶double类型值转换为float类型值,并将结果压入栈顶
B5: 15 putfield, 给对象字段赋值
00: 空
03: iconst_0, 将 0(int)推送至栈顶
B1: 18 return, void函数返回
""两个属性表""
00 00 00 02 00
0F 00 00 00 0E 00 03 00 00 00 03 00 04 00 05 00
0B 00 06 00 10 00 00 00 0C 00 01 00 00 00 13 00
11 00 12 00 00 00 09 00 13 00 14 00 02 00 0E 00
00 00: exception_table_length=0
00 02: attributes_count=2(Code属性表内部还含有2个属性表),
00 0F: attribute_name_index,转10进制为15,查找常量池 #15 为 LineNumberTable
00 00 00 0E: LineNumberTable表长度,16
00 03: line_number_table_length, line_number_info有3个
00 00: start_pc,字节码行号,0
00 03: 3
00 04: 4
00 05: 5
00 04: 4
00 0B: 11
00 06: 6
00 10: #16,LocalVariableTable
00 00 00 0C: attribute_length,12
00 01: local_variable_table_length,表示local_variable_info数量为1
00 00: start_pc,字节码行号0
00 13: length 19
00 11: name_index,#17 -> this
00 12: descriptor_index,#18 -> Lcom/example/demo/HelloWord;
00 00: index,0
第二个方法(大部分是重复方法一的内容,主要目的是对得上)
00 09 00 13 00 14 00 02 00 0E 00
00 00 5A 00 02 00 03 00 00 00 10 10 64 3C 11 00
00 09: 访问标志位 ACC_PUBLIC, ACC_STATIC
00 13: 方法名 #19 main
00 14: 描述符 #20 ([Ljava/lang/String;)V
00 02: 属性表 2个属性
00 0E: 属性名 #14 code
00 00 00 5A: 属性长度 90
""Code属性""
00 02: stack = 2
00 03: locals = 3
00 00 00 10: 属性长度,16
""函数栈""
10 64 3C 11 00
C8 3D B2 00 04 12 05 B6 00 06 B1 00 00 00 02 00
0.10: bipush 将一个byte值带符号扩展成int推送至栈顶
1.64: isub 将栈顶两int类型数相减,并将结果压入栈顶
2.3C: istore_1 将栈顶int类型值保存到局部变量1中
3.11: sipush 将一个short值带符号扩展成int推送至栈顶
4.00
5.C8: goto_w 无条件跳转到指定位置(宽索引)
6.3D: istore_2 将栈顶int类型值保存到局部变量2中
7.B2: getstatic 获取静态字段的值,并将其引用压入栈顶
8.00
9.04: iconst_1 将 1(int)推送至栈顶
10.12: ldc 将int、float或String型常量值从常量池中推送至栈顶
11.05: iconst_2 将 2(int)推送至栈顶
12.B6: invokevirtual 运行时方法绑定调用方法
13.00
14.06: iconst_3 将 3(int)推送至栈顶
15.B1: return void函数返回
""属性表""
00 00: exception_table_length=0
00 02: attributes_count=2(Code属性表内部还含有2个属性表)
0F 00 00 00 12 00 04 00 00 00 09 00 03 00 0A 00
07 00 0C 00 0F 00 0D 00 10 00 00 00 20 00 03 00
00 00 10 00 15 00 16 00 00 00 03 00 0D 00 17 00
0A 00 01 00 07 00 09 00 18 00 0A 00 02 00 19 00
00 0F: attribute_name_index #15 为 LineNumberTable
00 00 00 12: length 18
00 04: line_number_table_length, line_number_info有4个
00 00: start_pc, 0
00 09: line_number, 9
00 03: start_pc, 3
00 0A: line_number, 10
00 07: start_pc, 7
00 0C: line_number, 12
00 0F: start_pc, 13
00 0D: line_number, 15
00 10: #16,LocalVariableTable
00 00 00 20: length 32
00 03: local_variable_info数量为3
00 00: start_pc 0
00 10: length,16
00 15: name_index #21 -> args
00 16: descriptor_index #22 -> [Ljava/lang/String;
00 00: index 0
00 03: start_pc 3
00 0D: length, 13
00 17: name_index #23 -> a
00 0A: descriptor_index #10 -> I
00 01: index 1
00 07: start_pc 7
00 09: length 9
00 18: name_index #24 -> b
00 0A: descriptor_index #10 -> I
00 02: index 2
""MethodParameters属性""
00 19 00
00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
00 19: 属性名 #25 MethodParameters, 记录方法的各个形参名称和信息
00 00 00 05: length, 5
01: parameters_count, 1个参数
00 15: parameter.name_index, #21 -> args
00 00: access_flags,参数状态(0010被final修饰;1000编译器生成的;8000隐式定义的,如this关键字)
Attribute
00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
00 1B
00 01: 有1个Attributes
00 1A: name_index #26 -> SourceFile
00 00 00 02: attribute_length=2
00 1B: source_index #27 -> HelloWord.java
参考
这一次,彻底弄懂「Java字节码文件」 - 知乎 (zhihu.com)
《深入理解Java虚拟机》
一文让你明白Java字节码 - 简书 (jianshu.com)
ASCII码一览表,ASCII码对照表 (biancheng.net)
Gradle 插件 + ASM 实战 - JVM 虚拟机加载 Class 原理 - 掘金 (juejin.cn)
网友评论