[JVM] class字节码文件结构分析
参考文档Chapter 4. The class File Format
class字节码文件结构组成
总体结构
java源文件经java编译器编译后生成的class文件整体结构如下图所示:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- magic: 魔数,4个字节,为固定值
0xCAFEBABE
。jvm在加载class文件时,首先检查该class文件的前四个字节是否为0xCAFEBABE
,若不是则jvm认为该文件不是java的class文件; - minor_version,major_version: 分别占用2个字节,class文件的次版本号和主版本号;
- constant_pool_count: 2个字节,常量池中常量个数;
- constant_pool[]: 常量池表,根据常量池项内容的不同,大小也不一样,保存该类中所用到的所有常量,常量池表中每一项的大小都不是固定的,这一点和编程语言中的数组有区别,每一个常量池项的第一个字节用来标识常量池项的类型,不同的类型info具有不同的长度,稍后我们将介绍每一种常量池类型;
- access_flags: 2个字节,用来表示类的访问权限或者类的属性(是否是枚举、注解、抽象类等),取值如下图所示
- this_class:2个字节,指向常量池中代表该类并且类型为
CONSTANT_Class_info
的常量池项的索引; - super_class: 2个字节,指向常量池中代表该类的父类并且类型为
CONSTANT_Class_info
的常量池索引(java中只能继承一个类,可以实现多个接口); - interfaces_count:2个字节,该类所实现接口的数量;
- interfaces[interfaces_count]:每个元素两个字节,该类所实现的所有接口,每个元素都是指向常量池的索引;
- fields_count:2个字节,该类中字段的数量;
- fields[fields_count]:字段表,每个元素的类型是
field_info
,字段表中每个元素大小都可能不一样; - methods_count:2个字节,类中方法数量;
- methods[methods_count]:方法表,方法表中每个元素类型都是
method_info
,2元素的大小可能不一样; - attributes_count:2个字节,该类中属性的数量;
- attributes[attributes_count]:属性表,属性表中元素类型为
attribute_info
,每个元素大小可能不一样;
常量池信息cp_info
常量池表constant_pool[]
中每一项元素具有如下结构
cp_info {
u1 tag;
u1 info[];
}
其中第一个字节是tag
,标识该常量池元素的类型,如下图所示为常量池元素的11种数据类型及其结构信息
字段信息field_info
每个field_info中描述了类和接口中声明的一个变量(包括类变量以及实例变量,不包括方法内部声明的局部变量),field_info结构如下所示
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法信息method_info
方法表中一个method_into描述了一个方法的信息,method_info具有如下结构
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中attribute_info
同field_info
中的attribute_info
。
属性信息attribute_info
attribute_info
具有如下结构
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
attribute_info
用来描述ClassFile
、field_info
和method_info
中的属性信息。前两个字段是固定的,2个字节的attribute_name_index
表示常量池中该属性名称常量的索引,4个字节的attribute_length
表示接下来属性内容的长度,根据不同的属性类型info
数组所表示的内容也有所不同。如下示例为Code
属性结构
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
jvm在解析的时候会根据attribute_name_index
所指向的属性名称用不同的属性结构来解析info
数组,从而得到属性信息。
class字节码结构示例分析
我们以如下所示代码为例分析class文件的字节码结构
public class MyTest2 {
String str = "Hello World";
private int x = 5;
public static Integer in = 10;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public static void main(String[] args) {
MyTest2 myTest2 = new MyTest2();
myTest2.setX(8);
in = 2;
}
}
使用javap -verbose com.ctrip.flight.test.jvm.bytecode.MyTest2
命令反编译该class文件得到如下结果
Classfile /Users/peter/MyWork/study/IdeaWorkSpace/jvm/out/production/classes/com/ctrip/flight/test/jvm/bytecode/MyTest2.class
Last modified 2018-8-12; size 944 bytes
MD5 checksum da374f2c6579a47b4d9e25f99c22559b
Compiled from "MyTest2.java"
public class com.ctrip.flight.test.jvm.bytecode.MyTest2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#36 // java/lang/Object."<init>":()V
#2 = String #37 // Hello World
#3 = Fieldref #5.#38 // com/ctrip/flight/test/jvm/bytecode/MyTest2.str:Ljava/lang/String;
#4 = Fieldref #5.#39 // com/ctrip/flight/test/jvm/bytecode/MyTest2.x:I
#5 = Class #40 // com/ctrip/flight/test/jvm/bytecode/MyTest2
#6 = Methodref #5.#36 // com/ctrip/flight/test/jvm/bytecode/MyTest2."<init>":()V
#7 = Methodref #5.#41 // com/ctrip/flight/test/jvm/bytecode/MyTest2.setX:(I)V
#8 = Methodref #42.#43 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#9 = Fieldref #5.#44 // com/ctrip/flight/test/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;
#10 = Class #45 // java/lang/Object
#11 = Utf8 str
#12 = Utf8 Ljava/lang/String;
#13 = Utf8 x
#14 = Utf8 I
#15 = Utf8 in
#16 = Utf8 Ljava/lang/Integer;
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
#24 = Utf8 getX
#25 = Utf8 ()I
#26 = Utf8 setX
#27 = Utf8 (I)V
#28 = Utf8 main
#29 = Utf8 ([Ljava/lang/String;)V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 myTest2
#33 = Utf8 <clinit>
#34 = Utf8 SourceFile
#35 = Utf8 MyTest2.java
#36 = NameAndType #17:#18 // "<init>":()V
#37 = Utf8 Hello World
#38 = NameAndType #11:#12 // str:Ljava/lang/String;
#39 = NameAndType #13:#14 // x:I
#40 = Utf8 com/ctrip/flight/test/jvm/bytecode/MyTest2
#41 = NameAndType #26:#27 // setX:(I)V
#42 = Class #46 // java/lang/Integer
#43 = NameAndType #47:#48 // valueOf:(I)Ljava/lang/Integer;
#44 = NameAndType #15:#16 // in:Ljava/lang/Integer;
#45 = Utf8 java/lang/Object
#46 = Utf8 java/lang/Integer
#47 = Utf8 valueOf
#48 = Utf8 (I)Ljava/lang/Integer;
{
java.lang.String str;
descriptor: Ljava/lang/String;
flags:
public static java.lang.Integer in;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_STATIC
public com.ctrip.flight.test.jvm.bytecode.MyTest2();
descriptor: ()V
flags: 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: ldc #2 // String Hello World
7: putfield #3 // Field str:Ljava/lang/String;
10: aload_0
11: iconst_5
12: putfield #4 // Field x:I
15: return
LineNumberTable:
line 3: 0
line 5: 4
line 7: 10
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
public int getX();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #4 // Field x:I
4: ireturn
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
public void setX(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #4 // Field x:I
5: return
LineNumberTable:
line 16: 0
line 17: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
0 6 1 x I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/ctrip/flight/test/jvm/bytecode/MyTest2
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 8
11: invokevirtual #7 // Method setX:(I)V
14: iconst_2
15: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: putstatic #9 // Field in:Ljava/lang/Integer;
21: return
LineNumberTable:
line 20: 0
line 22: 8
line 24: 14
line 25: 21
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 args [Ljava/lang/String;
8 14 1 myTest2 Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 10
2: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: putstatic #9 // Field in:Ljava/lang/Integer;
8: return
LineNumberTable:
line 9: 0
}
SourceFile: "MyTest2.java"
生成的二进制class文件如下
我们将按照class文件结构一个一个字节的来分析该二进制文件,如下图所示的class文件结构
魔数Magic Number
处于class文件最开始的4个字节,值固定为CAFEBABE
,如下图所示
主版本号和次版本号
接下来的4个字节是主版本号和次版本号,前两个字节为次版本号,后两个字节为主版本号
从图中可以看出次版本号为0,主版本号为52(16进制0x34)。如果一个class文件是用高版本的java编译器编译的,如果把该class文件放在低版本的JDK上运行就会抛异常。
常量池Constant Pool
接下来的两个字节是常量池的长度,如下图所示
从图中可以看出常量池长度为49(16进制0x31),也就是接下来常量池表中有49个常量,每个常量的元素大小都不同,因此只能先确定前面一个常量内容后再确定后面一个常量的内容。回顾我们之前说到的常量池项的结构
cp_info {
u1 tag;
u1 info[];
}
第一个常量
第一个常量的第一个字节是tag
用来标识常量类型,这里第一个常量的第一个字节如下
tag
值为10(16进制的0x0A),常量池类型为CONSTANT_Methodref
,是一个方法信息,结构如下
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
class_index
值为10(16进制0x000A),name_and_type_index
值为36(16进制的0x0024)
结合
javap -verbose
得到的结果可知,序号为10和36的常量池项为
#10 = Class #45 // java/lang/Object
#36 = NameAndType #17:#18 // "<init>":()V
而常量池中第一个常量项为
#1 = Methodref #10.#36 // java/lang/Object."<init>":()V
结果吻合。
第二个常量
第二个常量tag
为8
tag
为8的类型为CONSTANT_String_info
,结构如下
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
也就是接下来的两个字节是一个指向字符串常量的索引。string_index
值为37(0x0025)
索引37对应的常量项为
#37 = Utf8 Hello World
而常量池第二个常量为
#2 = String #37 // Hello World
也对的上。
第三个常量
为简化描述从第三个常量开始我们把该常量所有字段全部标记出来。第三个字节码如下
tag
值为9,表示常量类型为CONSTANT_Fieldref
,CONSTANT_Fieldref
结构如下所示
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
因此class_index
值为5(0x0005),name_and_type_index
值为38(0x0026),对应的常量项为
#5 = Class #40 // com/ctrip/flight/test/jvm/bytecode/MyTest2
#38 = NameAndType #11:#12 // str:Ljava/lang/String;
而第三个常量项为
#3 = Fieldref #5.#38 // com/ctrip/flight/test/jvm/bytecode/MyTest2.str:Ljava/lang/String;
刚好对应
第四个常量项
第四个常量字节码如下
tag
值为9,同第三个常量,不再做分析。 推荐阅读
-
[JVM] class字节码文件结构分析
-
实例分析Class字节码文件(一)
-
jvm——字节码中的方法的执行分析
-
17.字节码中方法内部结构分析-Jclasslib的使用
-
JVM Class文件结构 博客分类: JVM jvm虚拟机java
-
[二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义
-
荐 【探究JVM四】Java方法执行的线程内存模型——虚拟机栈 字节码指令追踪,万字长文深入探究内部结构
-
实例分析Java Class的文件结构
-
实例分析Java Class的文件结构
-
请教一个Class文件操作码的意思 JVM虚拟机SUNHTML