Java类文件结构

kolbe 2021年09月17日 215次浏览

1 简介

Class文件采用类C语言结构体的伪结构存储数据,这种结构包含两种数据类型:无符号数和表

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];
}

1.1 无符号数

属于基本数据类型,以u1、u2、u4、u8代表1个字节、2个字节、4个字节、8个字节的无符号数

1.2 表

由多个无符号数或其它表作为数据项构成的复合数据类型,所以表以 _info 结尾

注:
Class结构中没有任何分隔符号,数据存储的字节序都是严格限定,不允许改变

2 魔数(magic)

每个Class文件前4个字节代表魔数(magic number),用来进行身份的识别(通过魔数区分文件类型),Java中魔数固定为 0xCAFEBABE

3 版本(version)

3.1 次版本号(minor_version)

第5个和第6个字节代表次版本号

3.2 主版本号(major_version)

第7个和第8个字节代表主版本号,Java的主版本号从45开始,每次升级大版本将对主版本号加1,高版本兼容低版本。

编译器版本主版本号
1.145
1.246
1.347
1.448
5.049
650
751
852
953
1054
1155
1256
1357

4 常量池(constant pool)

紧接着主次版本号之后是常量池。由于常量池数量不固定,所以2个字节的constant_pool_count来代表常量池的大小。容器的计数从1开始,0代表是不引用任何常量池。常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References),包括:

  • 类和接口全限定名
  • 字段名称和描述符
  • 方法名称和描述符

常量池中的每一项都是一个表(info),这些表的第一位为u1类型的标志位(tag),代表这个常量属于哪种类型

类型标志描述
CONSTANT_Utf81UTF-8编码的字符串
CONSTANT_Integer3整型字面量
CONSTANT_Float4浮点型字面量
CONSTANT_Long5长整型字面量
CONSTANT_Double6双精度浮点型字面量
CONSTANT_Class7类或接口的符号引用
CONSTANT_String8字符串类型的符号引用
CONSTANT_Fieldref9字段类型的符号引用
CONSTANT_Methodref10类中方法的符号引用
CONSTANT_InterfaceMethodref11接口中方法的符号引用
CONSTANT_NameAndType12字段或方法的部分符号引用
CONSTANT_MethodHandle15方法句柄
CONSTANT_MethodType16方法类型
CONSTANT_Dynamic17动态计算常量
CONSTANT_InvokeDynamic18动态方法调用点
CONSTANT_Module19一个模块
CONSTANT_Package20一个模块中开放或导出的包

4.1 CONSTANT_Utf8_info

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}
  • tag:固定值为1
  • length:代表UTF8字符串的长度(单位字节)
  • bytes:存放UTF8字符串的内容

4.2 CONSTANT_Integer_info

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}
  • tag:固定值3
  • bytes:按照高位在前存储int值

4.3 CONSTANT_Float_info

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}
  • tag:固定值4
  • bytes:按照高位在前存储float值

4.4 CONSTANT_Long_info

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
  • tag:固定值5
  • bytes:按照高位在前存储long值

4.5 CONSTANT_Double_info

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
  • tag:固定值6
  • bytes:按照高位在前存储double值

4.6 CONSTANT_Class_info

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}
  • tag:固定值为7
  • name_index:指向常量项索引,表示类全限定名

4.7 CONSTANT_String_info

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}
  • tag:固定值为8
  • string_index:指向字符串字面量的索引

4.8 CONSTANT_Fieldref_info

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

  • tag:固定值9
  • class_index:指向声明方法的接口索引项,类型为CONSTANT_Class_info
  • name_and_type_index:指向名称及类型的索引项,类型为CONSTANT_NameAndType_info

4.9 CONSTAONT_Methodref_info

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
  • tag:固定值10
  • class_index:指向声明方法的接口索引项,类型为CONSTANT_Class_info
  • name_and_type_index:指向名称及类型的索引项,类型为CONSTANT_NameAndType_info

4.10 CONSTANT_InterfaceMethodref_info

CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
  • tag:固定值11
  • class_index:指向声明方法的接口索引项,类型为CONSTANT_Class_info
  • name_and_type_index:指向名称及类型的索引项,类型为CONSTANT_NameAndType_info

4.11 CONSTANT_NameAndType_info

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}
  • tag:固定值12
  • name_index:字段或方法名的常量项的索引
  • descriptor_index:类型描述,表示方法签名或字段的类型

类型的字符串表示方法

字符串类型
Bbyte
Ddouble
Iint
Sshort
Vvoid
Cchar
Ffloat
Jlong
Zboolean
L;对象
[数组

注:
1)对于对象类型,以L + 类全限定名 + 分号(;),例如:Ljava/lang/Object; 表示类 java.lang.Object
2)对于数组类型,以左中括号([)作为标记,例如:String二维数组,使用 [[Ljava.lang.String; 表示

4.12 CONSTANT_MethodHandle_info

CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}
  • tag:固定值15
  • reference_kind:值为1至9之间,表示方法句柄的类型
  • reference_index:值为常量池的有效索引

4.13 CONSTANT_Dynamic_info

CONSTANT_Dynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}
  • tag:固定值17
  • bootstrap_method_attr_index:值为当前Class文件引导方法表的bootstrapmethods[]数组的索引
  • name_and_type_index:指向常量池的索引,表示方法名和方法描述(类型为CONSTANT_NameAndType_info结构)

4.14 CONSTANT_InvokeDynamic_info

CONSTANT_InvokeDynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}
  • tag:固定值18
  • bootstrap_method_attr_index:值为当前Class文件引导方法表的bootstrapmethods[]数组的索引
  • name_and_type_index:指向常量池的索引,表示方法名和方法描述(类型为CONSTANT_NameAndType_info结构)

4.15 CONSTANT_Module_info

CONSTANT_Module_info {
    u1 tag;
    u2 name_index;
}
  • tag:固定值19
  • name_index:指向常量池的索引,表示模块名

4.16 CONSTANT_Package_info

CONSTANT_Package_info {
    u1 tag;
    u2 name_index;
}
  • tag:固定值20
  • name_index:指向常量池的索引,表示包名称

5 访问标志(acccess_flags)

紧跟跟常量池之后的2个字节代表访问标志,用来识别类或接口的访问信息,包括:Class是类还是接口,是否是public类型,是否是abstract类型,是否被声明为final等,access_flags一共有16个标志位可以用(最大值0xFFFF),当前只定义了9个,没有使用的标志位要求一律为零

标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否声明为final
ACC_SUPER0x0020是否使用增强的方法调用父类方法
ACC_INTERFACE0x0200是否为接口
ACC_ABSTRACT0x0400是否为抽象类
ACC_SYNTHETIC0x1000是否是编译器产生的类,没有源码对应
ACC_ANNOTATION0x2000是否是注解
ACC_ENUM0x4000是否是枚举
ACC_MODULE0x8000是否是模块

6 类(this_class)、父类(super_class)、接口(interfaces)

类索引、父类索引、接口索引定义如下:

u2    this_class;
u2    super_class;
u2    interfaces_count;
u2    interfaces[interfaces_count];

类索引、父类索引、接口索引均使用u2类型的索引值,指向CONSTAINT_Class_info,并找到全限定名字符串。interfaces_count代表实现接口的数量,如果没有实现接口则为0。

7 字段(fields)

在接口描述之后,是类的字段信息,由于一个类会有多个字段,所以需要指出字段的个数,字段定义如下:

u2             fields_count;
field_info     fields[fields_count];

字段的数量由u2类型的fields_count决定,每一个字段均为field_info结构,field_info结构如下:

field_info {
    u2				access_flags;
    u2				name_index;
    u2				descriptor_index;
    u2				attributes_count;
    attribute_info	attributes[attributes_count];
}
  • access_flags
    访问标志和类的访问标志高度类似,具体如下
标志名称标志值含义
ACC_PUBLIC0x0001是否是public
ACC_PRIVATE0x0010是否是private
ACC_PROTECTED0x0020是否是protected
ACC_STATIC0x0200是否是static
ACC_FINAL0x0400是否是final
ACC_VOLATILE0x1000是否是volatile
ACC_TRANSIENT0x2000是否是transient
ACC_SYNTHETIC0x4000是否是编译器自动产生
ACC_ENUM0x8000是否是枚举
  • name_index:2个字节的整数,表示字段的名称索引,指向常量池中的CONSTANT_Utf8
  • descriptor_index:2个字节的整数,表示字段的类型索引,指向常量池中的CONSTANT_Utf8,具体参见4.11
  • attributes_count:一个字段可以有一些属性,比如初始化值,注释信息等,属性的个数存放在attributes_count中
  • attributes:属性的具体内容存放在attributes数组中,当为常量属性时,结构为
ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}
  • attribute_name_index:指向常量池,字符串值为"ConstantValue"
  • attribute_length:4个字节组成,表示属性的长度
  • constantvalue_index:指向常量池,代表常量的值类型

8 方法(methods)

在字段之后,就是类的方法,方法的结构与字段相似,具体结构如下:

u2             methods_count;
method_info    methods[methods_count];

method_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
  • method_count:代表类中的方法个数
  • methods:代表方法的具体结构
  • access_flags:方法的访问标记
标志名称标志值含义
ACC_PUBLIC0x0001是否是public
ACC_PRIVATE0x0002是否是private
ACC_PROTECTED0x0004是否是protected
ACC_STATIC0x0008是否是static
ACC_FINAL0x0010是否是final
ACC_SYNCHRONIZED0x0020是否是synchronized方法
ACC_BRIDGE0x0040是否是编译器产生的桥接方法
ACC_VARARGS0x0080是否是可变参数方法
ACC_NATIVE0x0100是否是本地方法
ACC_ABSTRACT0x0400是否是抽象方法
ACC_STRICT0x0800是否是浮点模式为FP-strict
ACC_SYNTHETIC0x1000是否是编译器产生的方法,没有原码对应
  • name_index:方法的名称,指向常量池
  • descriptor_index:方法的签名,指向常量池
  • attributes_count:属性的个数
  • attributes:属性详情
属性作用
Code表示方法的字节码
StackMapTableCode属性的描述属性,用于字节码变量类型验证
Exceptions方法的异常信息
SourceFile类文件的属性,表示生成这个类的源码
LineNumberTableCode属性的描述属性,描述行号和字节码的对应关系
LocalVariableTableCode属性的描述属性,描述函数局部变量表
BootstrapMethods类文件的描述属性,存放类的引导方法,用于invokeDynamic

8.1 Code

方法的主要内容存放在属性中,在属性里面最重要的一个属性就是Code,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;
    exception_info exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
exception_info {   
    u2 start_pc;
    u2 end_pc;
    u2 handler_pc;
    u2 catch_type;                            
} 
  • attribute_name_index:常量池索引,固定值为"Code"
  • attribute_length:属性值的长度
  • max_stack:操作数栈的最大深度
  • max_locals:局部变量表所需的最大空间
  • code_length:字节码长度
  • code[code_length]:字节码内容
  • exception_table_length:异常表项数量
  • exception_table[exception_table_length]:异常表项内容
  • attribute_count:属性长度
  • attributes[attributes_count]:属性内容

code属性本身也包含更多信息,包括行号、局部变量,主要包括如下信息:

  • LineNumberTable
  • LocalVariableTable
  • StackMapTable

8.2 StackMapTable

StackMapTable中含有若干个栈映射帧(Stack Map Frame)的数据,不包含运行时所需要的信息,仅用作Class文件的类型校验,结构如下:

StackMapTable_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              number_of_entries;
    stack_map_frame entries[number_of_entries];
}

union stack_map_frame {
    same_frame; 
    same_locals_1_stack_item_frame; 
    same_locals_1_stack_item_frame_extended;
    chop_frame;
    same_frame_extended;
    append_frame;
    full_frame;
}

每个栈映射帧是为了说明在一个特定的字节码偏移位置上,系统的数据类型是什么,包括局部变量表的类型和操作数栈的类型。

8.3 LineNumberTable

LineNumberTable用于记录字节码偏移量和行号的对应关系,结构如下:

LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    line_number_info  line_number_table[line_number_table_length];
}

line_number_info {   
    u2 start_pc; 
    u2 line_number;	
}

8.4 LocalVariableTable

这个属性也叫局部变量表,记录了一个方法中所有的局部变量,结构如下:

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    local_variable_info local_variable_table[local_variable_table_length];         
}

local_variable_info {   
    u2 start_pc;
    u2 length;
    u2 name_index;                                    
    u2 index; 
} 

附:

附1 属性

属性表(attribute_info)结构如下:

attrbiute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

每一个属性都必须有一个名称attribute_name_index,指向CONSTANT_Utf8_info常量,通过这个常量,可以确定该属性的info结构