Java字节码的整体结构:
Class字节码中有两种数据类型:
字节码数据直接量:这是基本的数据类型,共细分为u1、u2、u4、u84种类型,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据。
表(数组):表是由多个基本数据或者其他表按照既定的顺序组成的大的数据集合,表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。
上面的表描述了11种数据类型的结构,其实在JDK1.7之后,又增加了3种,
(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)这样一共存在14种数据类型。
Access_Flag访问标志:
访问标志信息包括Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明为final。
修饰符的表:
Tips: 少了一个0x0002表示的是ACC_PRIVATE
0x0021: 是0x0001和0x0020的并集,表示的是ACC_PUBLIC和ACC_SUPER
00 21 // 表示的是ACCESS_FLAG 修饰符A&修饰符B&... ACC_PUBLIC(0x0001)&ACC_SUPER(0x0020)
00 03 // 当前类的全局限定名称指向常量池 #3 = Class #22 // com/compass/spring_lecture/binarycode/MyTest1
00 04 //表示当前类的父类全局限定名称指向常量池 #4 = Class #23 // java/lang/Object
00 00 //表示接口的数量
00 01 //表示字段的数量 1
字段表的集合:
字段表用于描述类和接口中声明的变量,这里的字段包括类级别的变量及实例变量,但是不包括方法内部声明的局部变量。
字段表结构:
方法表的集合:
方法表的属性结构:
- JVM预定义了部分的attribute,但是编译器自己也可以实现自己的attribute并写入到class文件中,供运行时使用。
- 不同的attribute是通过attribute_name_index来区分的。
JVM规范预定义的属性:
方法中的每个属性都是一个atrribute_info结构:
Code结构:
Code attribute的作用是保存该方法的结构,如所对应的字节码结构:
Code结构:
- attribute_length表示attribute所包含的字节数,不包含arribute_name_index和attribute_length字段。
- max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
- max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
- code_length: 表示该方法所包含的字节码的字节数以及具体的指令码
- 具体字节码即是该方法被调用时,虚拟机所执行的代码
- exception_table,这里存放的是处理异常的信息
- 每个exception_table表项由start_pc、end_pc、handler_pc、catch_type组成
- start_pc和end_pc 表示在code数组中的从start_pc到end_pc处(包含start_pc不包含end_pc即([start_pc,end_pc)))的指令抛出异常会由这个表项来处理。
- handler_pc 表示处理异常的代码的开始处,catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常。
完整的字节码如下:
Classfile /C:/spring_lecture/target/classes/com/compass/spring_lecture/binarycode/MyTest1.class
Last modified 2019年6月26日; size 521 bytes
MD5 checksum e84ac7b4ed245fd5824849fb69bacc27
Compiled from "MyTest1.java"
public class com.compass.spring_lecture.binarycode.MyTest1
minor version: 0 //小版本
major version: 52 //大版本
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #3 // com/compass/spring_lecture/binarycode/MyTest1
super_class: #4 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool://常量池
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Fieldref #3.#21 // com/compass/spring_lecture/binarycode/MyTest1.a:I
#3 = Class #22 // com/compass/spring_lecture/binarycode/MyTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/compass/spring_lecture/binarycode/MyTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java
#20 = NameAndType #7:#8 // "":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/compass/spring_lecture/binarycode/MyTest1
#23 = Utf8 java/lang/Object
{
public com.compass.spring_lecture.binarycode.MyTest1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
----------------------------------------------------------------
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
----------------------------------------------------------------
LineNumberTable:
line 4: 0
line 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/compass/spring_lecture/binarycode/MyTest1;
public int getA();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
----------------------------------------------------------------
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
----------------------------------------------------------------
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/compass/spring_lecture/binarycode/MyTest1;
public void setA(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
----------------------------------------------------------------
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
----------------------------------------------------------------
LineNumberTable: //行号表
line 14: 0
line 15: 5
LocalVariableTable: //局部变量表
Start Length Slot Name Signature
0 6 0 this Lcom/compass/spring_lecture/binarycode/MyTest1;
0 6 1 a I
}
SourceFile: "MyTest1.java"
将常量池的字节码抽取出来如下:
Constant pool://常量池
序号 类型 引用
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Fieldref #3.#21 // com/compass/spring_lecture/binarycode/MyTest1.a:I
#3 = Class #22 // com/compass/spring_lecture/binarycode/MyTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/compass/spring_lecture/binarycode/MyTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java
#20 = NameAndType #7:#8 // "":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/compass/spring_lecture/binarycode/MyTest1
#23 = Utf8 java/lang/Object
字节码的16进制信息:
ca fe ba be 00 00 00 34 00 18 0a 00 04 00 14 09
00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00
01 49 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 2f 4c 63 6f 6d 2f 63 6f
6d 70 61 73 73 2f 73 70 72 69 6e 67 5f 6c 65 63
74 75 72 65 2f 62 69 6e 61 72 79 63 6f 64 65 2f
4d 79 54 65 73 74 31 3b 01 00 04 67 65 74 41 01
00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28
49 29 56 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
01 00 0c 4d 79 54 65 73 74 31 2e 6a 61 76 61 0c
00 07 00 08 0c 00 05 00 06 01 00 2d 63 6f 6d 2f
63 6f 6d 70 61 73 73 2f 73 70 72 69 6e 67 5f 6c
65 63 74 75 72 65 2f 62 69 6e 61 72 79 63 6f 64
65 2f 4d 79 54 65 73 74 31 01 00 10 6a 61 76 61
2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 00 21 00 03
00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03
00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02
00 01 00 00 00 0a 2a b7 00 01 2a 04 b5 00 02 b1
00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 04
00 04 00 06 00 0b 00 00 00 0c 00 01 00 00 00 0a
00 0c 00 0d 00 00 00 01 00 0e 00 0f 00 01 00 09
00 00 00 2f 00 01 00 01 00 00 00 05 2a b4 00 02
ac 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0a 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
0d 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00
3e 00 02 00 02 00 00 00 06 2a 1b b5 00 02 b1 00
00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 0e 00
05 00 0f 00 0b 00 00 00 16 00 02 00 00 00 06 00
0c 00 0d 00 00 00 00 00 06 00 05 00 06 00 01 00
01 00 12 00 00 00 02 00 13
对上述的16进制编码进行分析:
ca fe ba be //魔数
00 00 00 34 //版本信息 00 00 (次版本号) 00 34(52主版本号)
00 18 //CONSTANT_POOL START 常量池数量信息 00 16 (24)
0a 00 04 00 14 09 00 03 00 15 07 00 16 07 00
17 01 00 01 61 01 00 01 49 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 2f 4c 63 6f 6d 2f 63 6f 6d 70 61
73 73 2f 73 70 72 69 6e 67 5f 6c 65 63 74 75
72 65 2f 62 69 6e 61 72 79 63 6f 64 65 2f 4d
79 54 65 73 74 31 3b 01 00 04 67 65 74 41 01
00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28
49 29 56 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
01 00 0c 4d 79 54 65 73 74 31 2e 6a 61 76 61 0c
00 07 00 08 0c 00 05 00 06 01 00 2d 63 6f 6d 2f
63 6f 6d 70 61 73 73 2f 73 70 72 69 6e 67 5f 6c
65 63 74 75 72 65 2f 62 69 6e 61 72 79 63 6f 64
65 2f 4d 79 54 65 73 74 31 01 00 10 6a 61 76 61
2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 //CONSTANT_POOL END
00 21 // 表示的是ACCESS_FLAG 修饰符A&修饰符B&... ACC_PUBLIC(0x0001)&ACC_SUPER(0x0020)
00 03 // 当前类的全局限定名称指向常量池 #3 = Class #22 //
com/compass/spring_lecture/binarycode/MyTest1
00 04 //表示当前类的父类全局限定名称指向常量池 #4 = Class #23 // java/lang/Object
00 00 //表示接口的数量
00 01 //表示字段的数量 1
00 02 //字段的描述符ACCESS_FLAG ACC_PRIVATE(0x0002)
00 05 //5 表示字段的名称 #5 = Utf8 a
00 06 //6 表示字段的描述符也就是类型 #6 = Utf8 I
00 00 //字段的属性个数0
00 03 //表示方法的数量 3
00 01 //方法的访问标记 ACC_PUBLIC(0x0001)
00 07 //方法名字索引 #7 = Utf8
00 08 //方法的描述符索引 #8 = Utf8 ()V
00 01 //方法中属性的个数 1 方法的属性是this args_size=1
00 09 //表示的是属性名的索引 #9 = Utf8 Code(表示代码片段 方法的执行代码)
00 00 00 38 //U4 表示属性的长度 38=16*3+8=48+8=56 表示attribute所包含的字节数,不包含arribute_name_index和attribute_length字段
00 02 //U2 表示 max_stack stack=2 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
00 01 //U2 表示 max_local locals=1 表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
00 00 00 0a //表示的是code_length 10 //表示该方法所包含的字节码的字节数以及具体的指令码 具体字节码即是该方法被调用时,虚拟机所执行的代码
第6章 . JVM的机器执行指令集:
Chapter 6. The Java Virtual Machine Instruction Set :https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
2a b7 00 01 2a 04 b5 00 02 b1 //这10个字节对应方法的执行代码 这里需要参考JVM的规范:
---
2a ---aload_0 = 42 (0x2a)
Description:
The must be an index into the local variable array of the current frame (§2.6).
The local variable at must contain a reference. The objectref in the local
variable at is pushed onto the operand stack.
-------
b7 ---invokespecial = 183 (0xb7)
b7后面的2个字节
00 01 ---表示它接收的参数
Description:
调用了父类的构造器,详细信息见规范比较多
-------
2a ---aload_0 = 42 (0x2a)
Description:
The must be an index into the local variable array of the current frame (§2.6).
The local variable at must contain a reference. The objectref in the local
variable at is pushed onto the operand stack.
-------
04 ---iconst_1 = 4 (0x4)
Description:
Push the int constant (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
-------
b5 ---putfield = 181 (0xb5)
Description:
给成员变量赋值
The unsigned indexbyte1 and indexbyte2 are used to construct an index into the run-time constant pool of the current class (§2.6), where the value of the index is (indexbyte1 << 8) | indexbyte2. The run-time constant pool item at that index must be a symbolic reference to a field (§5.1), which gives the name and descriptor of the field as well as a symbolic reference to the class in which the field is to be found. The class of objectref must not be an array. If the field is protected, and it is a member of a superclass of the current class, and the field is not declared in the same run-time package (§5.3) as the current class, then the class of objectref must be either the current class or a subclass of the current class.
The referenced field is resolved (§5.4.3.2). The type of a value stored by a putfield instruction must be compatible with the descriptor of the referenced field (§4.3.2). If the field descriptor type is boolean, byte, char, short, or int, then the value must be an int. If the field descriptor type is float, long, or double, then the value must be a float, long, or double, respectively. If the field descriptor type is a reference type, then the value must be of a type that is assignment compatible (JLS §5.2) with the field descriptor type. If the field is final, it must be declared in the current class, and the instruction must occur in an instance initialization method () of the current class (§2.9).
The value and objectref are popped from the operand stack. The objectref must be of type reference. The value undergoes value set conversion (§2.8.3), resulting in value', and the referenced field in objectref is set to value'.
-------
00 02 --- 表示putfield的参数
Description:
#2 = Fieldref #3.#21 // com/compass/spring_lecture/binarycode/MyTest1.a:I
-------
b1 --- return = 177 (0xb1)
Description:
The current method must have return type void. If the current method is a synchronized method, the monitor entered or reentered on invocation of the method is updated and possibly exited as if by execution of a monitorexit instruction (§monitorexit) in the current thread. If no exception is thrown, any values on the operand stack of the current frame (§2.6) are discarded.
The interpreter then returns control to the invoker of the method, reinstating the frame of the invoker.
-------
00 00 //表示这个方法的异常表
00 02 //表示这个方法存在2个属性(不是成员变量的概念 不要混淆)
附加属性:
- 接下来就是方法的附加属性
-
LineNumberTable: 这个属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。
00 0a //10---#10 = Utf8 LineNumberTable 行号表
00 00 00 0a //10 行号表属性的长度
00 02 00 00 00 04 00 04 00 06 //行号信息
00 02: 表示存在几对映射(10-2)/2=4 4个字节一个映射关系
00 00 00 04:字节码的偏移量0映射的行号为4
00 04 00 06:字节码的偏移量4映射的行号为6
可以对照jclasslib的结果来验证自己的分析结果:
-------
00 0b //表示局部变量表 11
00 00 00 0c //表示局部变量表的长度 12
00 01 //局部变量的个数 1
00 00 //表示局部变量的开始位置
00 0a //表示局部变量的结束位置
00 //索引是0
0c //对应常量池的位置 12 #12 = Utf8 this 是编译器默认作为方法的第1个参数传进来 ---成员方法
00 0d // 当前对象的描述符 #13 = Utf8 Lcom/compass/spring_lecture/binarycode/MyTest1;
00 00 // jdk 1.6 之后的javac 提供了StackMapTable 属性长度是1 用于校验
-------
00 01 //方法的访问标记 ACC_PUBLIC(0x0001)
00 0e //方法的名字索引 #14 = Utf8 getA
00 0f //方法的描述符索引 #15 = Utf8 ()I
00 01 //方法中属性的个数 1 方法的属性是this args_size=1
00 09 //表示的是属性名的索引 #9 = Utf8 Code(表示代码片段 方法的执行代码)
00 00 00 2f //U4 表示属性的长度 2f=16*2+15=47 表示attribute所包含的字节数,不包含arribute_name_index和attribute_length字段
00 01 //U2 表示 max_stack stack=1 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
00 01 //U2 表示 max_local locals=1 表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
00 00 00 05 //U4 表示的是code_length 5 //表示该方法所包含的字节码的字节数以及具体的指令码 具体字节码即是该方法被调用时,虚拟机所执行的代码
-------
2a b4 00 02 ac //参考JVM Introductuion Set Specification
0 aload_0(0x2a)
1 getfield(0xb4) #2(0x 00 02)
4 ireturn(0xac)
00 00 //表示这个方法的异常表
00 02 //表示这个方法存在2个属性(不是成员变量的概念 不要混淆)
00 0a //10---#10 = Utf8 LineNumberTable 行号表
00 00 00 06 //6 行号表属性的长度
00 01 //表示存在几对映射(6-2)/1=4 表示4个字节一个映射
00 00 00 0a // 字节码的偏移量映射0的行号为12
00 0b //表示局部变量表 11
00 00 00 0c //表示局部变量表的长度 12
00 01 //局部变量的个数 1
00 00 //表示局部变量的开始位置
00 05 //表示局部变量的结束位置
00 //索引是0
0c //对应常量池的位置 12 #12 = Utf8 this 是编译器默认作为方法的第1个参数传进来 ---成员方法
00 0d // 当前对象的描述 #13 = Utf8 Lcom/compass/spring_lecture/binarycode/MyTest1;
00 00 // jdk 1.6 之后的javac 提供了StackMapTable 属性长度是1 用于校验
-------
00 01 //方法的访问标记 ACC_PUBLIC(0x0001)
00 10 //#16 = Utf8 setA
00 11 //#17 = Utf8 (I)V
00 01 //方法中属性的个数 1 方法的属性是this args_size=1
00 09 //表示的是属性名的索引 #9 = Utf8 Code(表示代码片段 方法的执行代码)
00 00 00 3e//U4 表示属性的长度 3e=16*3+14=48+14=62 表示attribute所包含的字节数,不包含arribute_name_index和attribute_length字段
00 02 //U2 表示 max_stack stack=1 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
00 02 //U2 表示 max_stack stack=1 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
00 00 00 06 //U4 表示的是code_length 6 //表示该方法所包含的字节码的字节数以及具体的指令码 具体字节码即是该方法被调用时,虚拟机所执行的代码
2a 1b b5 00 02 b1
0 aload_0(0x2a)
1 iload_1(0x1b)
2 putfield (0xb5)#2(0x0002)
5 return(0xb1)
00 00 //表示这个方法的异常表
00 02 //表示这个方法存在2个属性(不是成员变量的概念 不要混淆)
00 0a //10---#10 = Utf8 LineNumberTable 行号表
00 00 00 0a //10 行号表属性的长度
00 02//表示存在几对映射(10-2)/1=4 表示4个字节一个映射
00 00 00 0e//字节码的偏移量映射0的行号为14
00 05 00 0f //字节码的偏移量映射5的行号为15
00 0b////表示局部变量表 11
00 00 00 16//表示局部变量表的长度 22
----------------
00 02 //局部变量的个数 2
00 00 //表示局部变量的开始位置
00 06 //表示局部变量的结束位置
00 //索引是0
0c //对应常量池的位置 12 #12 = Utf8 this 是编译器默认作为方法的第1个参数传进来 ---成员方法
00 0d // 当前对象的描述符 #13 = Utf8 Lcom/compass/spring_lecture/binarycode/MyTest1;
00 00 //jdk 1.6 之后的javac 提供了StackMapTable 属性长度是1 用于校验
00 00 //起始位置是0
00 06 //结束位置是6
00 //索引是0
05 //#5 = Utf8 a
00 06 // #6 = Utf8 I
00 01 //jdk 1.6 之后的javac 提供了StackMapTable 属性长度是1 用于校验
属性内容
00 01//一个属性
00 12//对应常量池的18 #18 = Utf8 SourceFile
00 00 00 02//长度是2 后面的两个字节
00 13//对应源文件
附加属性
- 接下来就是方法的附加属性
-
LineNumberTable: 这个属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。