以 Java 类示例 中的Rectangle 类为示例
先用 javac 命令编译为 class文件,再使用 javap -v xxx.class 查看结构
Classfile /Users/yuanzeng/IdeaProject/untitled/src/Rectangle.class
Last modified 2020-12-20; size 501 bytes
MD5 checksum 182ab4527e63a234a830ebc7c1f29d36
Compiled from "Rectangle.java"
public class Rectangle implements Shape
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#25 // Rectangle.width:I
#3 = Fieldref #4.#26 // Rectangle.length:I
#4 = Class #27 // Rectangle
#5 = String #28 // 矩形
#6 = Class #29 // java/lang/Object
#7 = Class #30 // Shape
#8 = Utf8 TAG
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 ConstantValue
#11 = Utf8 width
#12 = Utf8 I
#13 = Utf8 length
#14 = Utf8 <init>
#15 = Utf8 (II)V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 getArea
#19 = Utf8 ()I
#20 = Utf8 getName
#21 = Utf8 ()Ljava/lang/String;
#22 = Utf8 SourceFile
#23 = Utf8 Rectangle.java
#24 = NameAndType #14:#31 // "<init>":()V
#25 = NameAndType #11:#12 // width:I
#26 = NameAndType #13:#12 // length:I
#27 = Utf8 Rectangle
#28 = Utf8 矩形
#29 = Utf8 java/lang/Object
#30 = Utf8 Shape
#31 = Utf8 ()V
{
public Rectangle(int, int);
descriptor: (II)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #2 // Field width:I
9: aload_0
10: iload_2
11: putfield #3 // Field length:I
14: return
LineNumberTable:
line 12: 0
line 13: 4
line 14: 9
line 15: 14
public int getArea();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field width:I
4: aload_0
5: getfield #3 // Field length:I
8: imul
9: ireturn
LineNumberTable:
line 19: 0
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: ldc #5 // String 矩形
2: areturn
LineNumberTable:
line 24: 0
}
SourceFile: "Rectangle.java"
16进制数据如下
CAFE BABE 0000 0034 0020 0A00 0600 1809 0004 0019 0900 0400 1A07 001B 0800 1C07 001D 0700 1E01 0003 5441 4701 0012 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 0100 0D43 6F6E 7374 616E 7456 616C 7565 0100 0577 6964 7468 0100 0149 0100 066C 656E 6774 6801 0006 3C69 6E69 743E 0100 0528 4949 2956 0100 0443 6F64 6501 000F 4C69 6E65 4E75 6D62 6572 5461 626C 6501 0007 6765 7441 7265 6101 0003 2829 4901 0007 6765 744E 616D 6501 0014 2829 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 0100 0A53 6F75 7263 6546 696C 6501 000E 5265 6374 616E 676C 652E 6A61 7661 0C00 0E00 1F0C 000B 000C 0C00 0D00 0C01 0009 5265 6374 616E 676C 6501 0006 E79F A9E5 BDA2 0100 106A 6176 612F 6C61 6E67 2F4F 626A 6563 7401 0005 5368 6170 6501 0003 2829 5600 2100 0400 0600 0100 0700 0300 1A00 0800 0900 0100 0A00 0000 0200 0500 0200 0B00 0C00 0000 0200 0D00 0C00 0000 0300 0100 0E00 0F00 0100 1000 0000 3300 0200 0300 0000 0F2A B700 012A 1BB5 0002 2A1C B500 03B1 0000 0001 0011 0000 0012 0004 0000 000C 0004 000D 0009 000E 000E 000F 0001 0012 0013 0001 0010 0000 0022 0002 0001 0000 000A 2AB4 0002 2AB4 0003 68AC 0000 0001 0011 0000 0006 0001 0000 0013 0001 0014 0015 0001 0010 0000 001B 0001 0001 0000 0003 1205 B000 0000 0100 1100 0000 0600 0100 0000 1800 0100 1600 0000 0200 17
Class 文件基本结构概述
- Class文件是一组以8位字节为基础单位的二进制流,当遇到需要8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。
- Class文件由无符号数和表构成。
- 无符号数:以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值、按照UTF-8编码构成的字符串值。
- 表:由多个无符号数或其他表作为数据项构成的复杂数据类型,所有表都习惯性地以“_info”结尾。
Class 文件格式如下:
类型 | 描述 | 备注 |
---|---|---|
u4 | magic | 魔数:0xCAFEBABE |
u2 | minor_version | 小版本号 |
u2 | major_version | 主版本号 |
u2 | constant_pool_count | 常量池大小,从1开始 |
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] | 属性表信息 |
魔数
魔数 CAFE BABE 是 JVM 识别 .class 文件的标志,虚拟机在加载文件之前会先检查这4个字节
版本号
0000 0034
0000 表示 minor version,次版本号
0034 表示 major version,主版本号,十进制是52,表示采用的是jdk1.8
常量池
0020 表示常量池的大小为32,实际索引为 1~31,常量池计数是从1而不是0开始。常量池中主要存放2大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量主要指文本字符串、被声明为final的常量值。
符号引用主要包括以下3类:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池中的每个常量都是一个表,共有11种不同的表结构,它们有一个共同的特点,就是表开始的第一位都是一个u1类型的标志位(tag,取值为1到12,缺少标志为2的数据类型)。tag表示的数据类型如下表所示:
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整形字面量,boolean、byte、char、short等类型都用int存放 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示invokedynamic指令 |
以下是一份常量池数据结构总表:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 | 值为1 |
length | u2 | UTF-8编码的字符串占用了字节数 | |
bytes | u1 | 长度为length的UTF-8编码的字符串 | |
CONSTANT_Integer_info | tag | u1 | 值为3 |
bytes | u4 | 按照高位在前存储的int值 | |
CONSTANT_Float_info | tag | u1 | 值为4 |
bytes | u4 | 按照高位在前存储的float值 | |
CONSTANT_Long_info | bytes | u1 | 值为5 |
tag | u8 | 按照高位在前存储的long值 | |
CONSTANT_Double_info | tag | u1 | 值为6 |
bytes | u8 | 按照高位在前存储的double值 | |
CONSTANT_Class_info | tag | u1 | 值为7 |
index | u2 | 指向全限定名常量项的索引 | |
CONSTANT_String_info | tag | u1 | 值为8 |
index | u2 | 指向字符串字面量的索引 | |
CONSTANT_Fieldref_info | tag | u1 | 值为9 |
class_index | u2 | 指向声明字段的类或接口描述符CONSTANT_Class_info的索引项 | |
name_and_type_index | u2 | 指向字段描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_Methodref_info | tag | u1 | 值为10 |
class_index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
name_and_type_index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_InterfaceMethodref_info | tag | u1 | 值为11 |
class_index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | |
name_and_type_index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | |
CONSTANT_NameAndType_info | tag | u1 | 值为12 |
name_index | u2 | 指向该字段或方法名称常量项的索引 | |
descriptor_index | u2 | 指向该字段或方法描述符常量项的索引 | |
CONSTANT_MethodHandle_info | tag | u1 | 值为15 |
reference_kind | u2 | 值必须在1一9之间(包括1和9).它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为 | |
reference_index | u2 | 值须是对常量池的有效索引 | |
CONSTANT_MethodType_info | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utt8_info结构,表示方法的描述符 | |
CONSTANT_InvokeDynamic_info | tag | u1 | 值为18 |
bootstrap_method_attr_index | u2 | 值必须是对当前Cass文件中引导方法表的bootstrap_methodsl[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
访问标记
常量池之后,是2个字节来表示访问标志,用于识别一些类或者接口层次的访问信息
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | JDK1.0.2以后这个标志都为真,不再使用 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或抽象类来说,此标志值为真,其他类值为假 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
针对这个类来说,其访问标志应该是ACC_PUBLIC、ACC_SUPER这2个标志为真,所以其值为 0x0001 | 0x0020 = 0x0021,上述16进制数据 5600 2100 中的 00 21
类索引、父类索引
访问标志后的2个字节0004 = 4,指向常量池里的第4个常量,接下来的0006 = 6,指向常量池里的第6个常量(该类的父类是Object)
#4 = Class #27 // Rectangle
#6 = Class #29 // java/lang/Object
#27 = Utf8 Rectangle
#29 = Utf8 java/lang/Object
接口索引
接口个数 00 01 表示只有一个接口,00 07 标识第一个接口指向 第7个常量
#7 = Class #30 // Shape
#30 = Utf8 Shape
字段表
接口索引后面紧跟着的是字段表信息,用于描述接口或类中声明的变量,不包括方法内部的变量,字段表的入口前2个字节表示字段的个数,00 03 表示有3个字段(TAG、width、length),后面紧跟着的是字段的描述表。字段信息接口表如下:
类型 | 描述 | 备注 |
---|---|---|
u2 | access_flags | 记录字段的访问标志 |
u2 | name_index | 常量池中的索引项,指定字段的名称 |
u2 | descriptor_index | 常量池中的索引项,指定字段的描述符 |
u2 | attributes_count | 包含的attribute数 |
attribute_info | attributes[attributes_count] | 每一个attributes的具体结构 |
其中 access_flags 字段访问标识如下:
权限名称 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | public |
ACC_PRIVATE | 0x0002 | private |
ACC_PROTECTED | 0x0004 | protected |
ACC_STATIC | 0x0008 | static,静态 |
ACC_FINAL | 0x0010 | final |
ACC_VOLATILE | 0x0040 | volatile,不可和ACC_FIANL一起使用 |
ACC_TRANSIENT | 0x0080 | 在序列化中被忽略的字段 |
ACC_SYNTHETIC | 0x1000 | 由编译器产生,不存在于源代码中 |
ACC_ENUM | 0x4000 | 枚举类型 |
紧随access_flags标志的是name_index和descriptor_index,他们都是对常量池的引用。name_index代表着字段的简单名称,descriptor_index代表着字段的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。
描述符标识字符含义:
标识字符 | 含义 |
---|---|
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L | 对象类型,如Ljava/lang/Object; |
[ | 数组类型,多个维度则有多个[ |
用描述符来描述方法时,按照先参数列表后返回值的顺序描述,参数列表按照参数的严格顺序放在一组“()”之内
以上文中 TAG 字段为例分析,涉及相关数据为:
00 1A00 0800 0900 0100 0A00 0000 0200 05
- 00 1A 表示 access_flags,private static final 值为 0x0002 | 0x00008 | 0x0010 = 0x001A
00 08 表示 name_index,值为 TAG
#8 = Utf8 TAG
00 09 表示 descriptor_index
#9 = Utf8 Ljava/lang/String;
00 01 表示有一个属性
- 00 0A 表示属性name,attribute_name_index 值为 0x0A = 10,为 ConstantValue 属性,还有其他的属性集合,后文分析
#10 = Utf8 ConstantValue
ConstantValue属性的结构为
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constant_value_index;
}
attribute_length 在 ConstantValue_attribute 属性值固定为 02,因为接下来的内容值只有2个字节大小(常量值索引)
所以 TAG 字段表中 ConstantValue 属性的常量值索引为:0x05
#5 = String #28 // 矩形
综上,则 TAG 定义为:private static final String TAG = “矩形”;
方法表
方法表的结构与字段表的结构是一样的
类型 | 描述 | 备注 |
---|---|---|
u2 | access_flags | 记录方法的访问标志 |
u2 | name_index | 常量池中的索引项,指定方法的名称 |
u2 | descriptor_index | 常量池中的索引项,指定方法的描述符 |
u2 | attributes_count | 包含的attribute数 |
attribute_info | attributes[attributes_count] | 每一个attributes的具体结构 |
方法访问标识比字段访问标识丰富,一共有12种
方法访问标记 | 特征值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | public |
ACC_PRIVATE | 0x0002 | private |
ACC_PROTECTED | 0x0004 | protected |
ACC_STATIC | 0x0008 | static |
ACC_FINAL | 0x0010 | final |
ACC_SYNCHRONIZED | 0x0020 | synchronized |
ACC_BRIDGE | 0x0040 | bridge 方法, 由编译器生成 |
ACC_VARARGS | 0x0080 | 方法包含可变长度参数,比如 String… args |
ACC_NATIVE | 0x0100 | native |
ACC_ABSTRACT | 0x0400 | abstract |
ACC_STRICT | 0x0800 | 声明为 strictfp,表示使用 IEEE-754 规范的精确浮点数,极少使用 |
ACC_SYNTHETIC | 0x1000 | 表示这个方法是由编译器自动生成,而不是用户代码编译产生 |
0x0003 表示有3个方法,这里以 getName() 方法为例分析,相关数据为:
0001 0014 0015 0001 0010 0000 001B 0001 0001 0000 0003 1205 B000 0000 0100 100 0000 0600 0100 0000 18
- access_flags 值为 0x0001,表示这是一个 public 方法
name_index 值为 0x0014,换算为10进制是20
#20 = Utf8 getName
descriptor_index 值为 0x0015,换算为10进制是21
#21 = Utf8 ()Ljava/lang/String;
表计数器为0x0001,表示只有一个属性,接下来就是该方法的第一个属性表。第一个属性表对应的属性名称索引值为0x0010,十进制为16,对应常量值为“Code”,说明此属性是方法的字节码描述,这个属性就存储了方法里的java代码编译后的字节码指令,具体字节码指令不再分析
#16 = Utf8 Code
属性表
在Class文件、字段表、方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |
具体属性内容单开一篇文章分析,例中属性表相关数据为:
00 0100 1600 0000 0200 17
- 0x0001 表示此例中只有 1个属性
0x0016 换算10进制的值为 22,表示 attribute_name_index = 22,表示属性为SourceFile 属性
#22 = Utf8 SourceFile
0x00000002 表示 attribute_length, 值为 2
- 0x0017 表示sourcefile_name_index,换算为10进制为 23
截止至此,整个 Class 的结构就算结束了#23 = Utf8 Rectangle.java
参考&致谢: Java Class文件结构解析
工具推荐
推荐一个查看分析字节码的工具 010 Editor,配合 Template CLASSAdv.bt 查看字节码
当前定位到值是类访问标识属性,可以看到16进制数据中的位置,以及字段结构,10进制转换等一系列方便的操作