Class文件笔记

2020-04-09  本文已影响0人  R7_Perfect

一、class文件结构

class文件用文本编辑器打开:

cafe babe 0000 0034 0014 0700 1207 0013
0100 1a44 4546 4155 4c54 5f41 4e49 4d41
5449 4f4e 5f44 5552 4154 494f 4e01 0001
4a01 000d 436f 6e73 7461 6e74 5661 6c75
6505 0000 0000 0000 01f4 0100 0e73 7461
7274 416e 696d 6174 696f 6e01 0004 284a
2956 0100 0f63 616e 6365 6c41 6e69 6d61
7469 6f6e 0100 0328 2956 0100 1269 7341
6e69 6d61 7469 6f6e 5374 6172 7465 6401
0003 2829 5a01 0019 7365 7443 6861 7274
416e 696d 6174 696f 6e4c 6973 7465 6e65
7201 003b 284c 6c65 6368 6f2f 6c69 622f
6865 6c6c 6f63 6861 7274 732f 616e 696d
6174 696f 6e2f 4368 6172 7441 6e69 6d61
7469 6f6e 4c69 7374 656e 6572 3b29 5601
000a 536f 7572 6365 4669 6c65 0100 1643
6861 7274 4461 7461 416e 696d 6174 6f72
2e6a 6176 6101 0031 6c65 6368 6f2f 6c69
622f 6865 6c6c 6f63 6861 7274 732f 616e
696d 6174 696f 6e2f 4368 6172 7444 6174
6141 6e69 6d61 746f 7201 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0601 0001
0002 0000 0001 0019 0003 0004 0001 0005
0000 0002 0006 0004 0401 0008 0009 0000
0401 000a 000b 0000 0401 000c 000d 0000
0401 000e 000f 0000 0001 0010 0000 0002
0011 

字节码文件是由 十六进制值组成 的,对于 JVM 来说,在读取数据的时候,它会 以两个十六进制值为一组,即 一个字节 进行读取。
根据 JVM 规范的规定,Class 文件格式采用了一种类似于 C 语言结构体的伪结构来存 储数据,而这种伪结构中有且只有两种数据类型:无符号数和表。

1、无符号数

无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来 描述数字、索引引用、数量值或者按照UTF-8 码构成字符串值

2、表

u4:表示能够保存4个字节的无符号整数,u2同理。

ClassFile { 
    u4 magic;  // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE(咖啡宝贝)
    u2 minor_version; // 分别为Class文件的副版本和主版本
    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. magic:每个 Class 文件的头 4 个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。很多文件存储标准中都使用魔数来进行身份识别, 譬如图片格式,如 gif 或者 jpeg 等在文件头中都存有魔数。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。并且,Class 文件的魔数获得很有 “浪漫气息”,值为:0xCAFEBABE(咖啡宝贝)。
  2. minor_version:2 个字节长,表示当前 Class 文件的次版号。
  3. major_version:2 个字节长,表示当前 Class 文件的主版本号。(Java 的版本号是从 45 开始 的,JDK 1.1 之后的每个 JDK 大版本发布会在主版本号向上加 1(JDK 1.0~1.1 使用了 45.0~45.3 的版本号),例如 JDK 1.8 就是 52.0)。需要注意的是,虚拟机会拒绝执行超过其版本号的 Class 文件。
  4. constant_pool_count:常量池数组元素个数。
  5. constant_pool:常量池,是一个存储了 cp_info 信息的数组,每一个 Class 文件都有一个与之对应的常量池。(注意:cp_info 数组的索引从 1 开始)
  6. access_flags:表示当前类的访问权限,例如:public、private。
  7. this_class 和 super_class:存储了指向常量池数组元素的索引,this_class 中索引指向的内容为当前类名,而 super_class 中索引则指向其父类类名。
  8. interfaces_count 和 interfaces:同上,它们存储的也只是指向常量池数组元素的索引。其内容分别表示当前类实现了多少个接口和对应的接口类类名。
  9. fields_count 和 fields:表示成员变量的数量和其信息,信息由 field_info 结构体表示。
  10. methods_count 和 methods:表示成员函数的数量和它们的信息,信息由 method_info 结构体表示。
  11. attributes_count 和 attributes:表示当前类的属性信息,每一个属性都有一个与之对应的 attribute_info 结构。常见的属性信息如调试信息,它需要记录某句代码对应源代码的哪一行,此外,如函数对应的 JVM 字节码、注解信息也是属性信息。

二、常量池

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)

常量项 Utf8

常量项 Utf8 的数据结构如下所示:

CONSTANT_Utf8_info {
    u1 tag; 
    u2 length; 
    u1 bytes[length]; 
}
  1. tag:值为 1,表示是 CONSTANT_Utf8_info 类型表。
  2. length:length 表示 bytes 的长度,比如 length = 10,则表示接下来的数据是 10 个连续的 u1 类型数据。
  3. bytes:u1 类型数组,保存有真正的常量数据。

常量项 Class、Filed、Method、Interface、String

CONSATNT_Class_info {
    u1 tag;
    u2 name_index; 
}

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

CONSTANT_MethodType_info {
    u1 tag;
    u2 descriptor_index;
}

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

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

CONSATNT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index
}

name_index:指向常量池中索引为 name_index 的常量表。比如 name_index = 6,表明它指向常量池中第 6 个常量。
class_index:指向当前方法、字段等的所属类的引用。
name_and_type_index:指向当前方法、字段等的名字和类型的引用。
name_index:指向某字段或方法等的名称字符串的引用。
descriptor_index:指向某字段或方法等的类型字符串的引用。

注意:CONSTANT_String 和 CONSTANT_Utf8 的区别

CONSTANT_Utf8:真正存储了字符串的内容,其对应的数据结构中有一个字节数组,字符串便酝酿其中。
CONSTANT_String:本身不包含字符串的内容,但其具有一个指向 CONSTANT_Utf8 常量项的索引。

常量项 Integer、Long、Float、Double

CONSATNT_Integer_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Float_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

三、信息描述规则

1、数据类型

  1. 原始数据类型:
    Java 类型的 byte、char、double、float、int、long、short、boolean => "B"、"C"、"D"、"F"、"I"、"J"、"S"、"Z"。

  2. 引用数据类型:
    ClassName => L + 全路径类名(其中的 "." 替换为 "/",最后加分号),例如 String => Ljava/lang/String。

  3. 数组(引用类型):
    不同类型的数组 => "[该类型对应的描述名",例如 int 数组 => "[I",String 数组 => "[Ljava/lang/Sting",二维 int 数组 => "[[I"。

2、成员变量

在 JVM 规范之中,成员变量即 Field Descriptor 的描述规则如下所示:

FiledDescriptor:
# 1、仅包含 FieldType 一种信息
FieldType
FiledType:
# 2、FiledType 的可选类型
BaseType | ObjectType | ArrayType
BaseType:
B | C | D | F | I | J | S | Z
ObjectType:
L + 全路径ClassName;
ArrayType:
[ComponentType:
# 3、与 FiledType 的可选类型一样
ComponentType:

3、成员函数描述规则

在 JVM 规范之中,成员函数即 Method Descriptor 的描述规则如下所示:

MethodDescriptor:
# 1、括号内的是参数的数据类型描述,* 表示有 0 至多个 ParameterDescriptor,最后是返回值类型描述
( ParameterDescriptor* ) ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType | VoidDescriptor
VoidDescriptor:
// 2、void 的描述规则为 "V"
V

四、filed_info 与 method_info

field_info {
    u2              access_flags;
    u2              name
    u2              descriptor_index
    u2              attributes_count
    attribute_info  attributes[attributes_count]
}

method_info {
    u2              access_flags;
    u2              name
    u2              descriptor_index
    u2              attributes_count
    attribute_info  attributes[attributes_count]
}

可以看到,filed_info 与 method_info 都包含有 访问标志、名字引用、描述信息、属性数量与存储属性 的数据结构。对于 method_info 所描述的成员函数来说,它的内容经过编译之后得到的 Java 字节码会保存在属性之中。
注意:类构造器为 “< clinit >” 方法,而实例构造器为 “< init >” 方法。

五、access_flags

1、Class 的 access_flags 取值类型

标志名 标志值 标志含义
ACC_PUBLIC 0x0001 public类型
ACC_FINAL 0x0010 final类型
ACC_SUPER 0x0020 使用新的invokespecial语义
ACC_INTERFACE 0x0200 接口类型
ACC_ABSTRACT 0x0400 抽象类型
ACC_SYNTHETIC 0x1000 该类不由用户代码生成
ACC_ANNOTATION 0x2000 注解类型
ACC_ENUM 0x4000 枚举类型

2、Filed 的 access_flag 取值类型

标志名 标志值 标志含义
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_TRANSIENT 0x0080 transient,不能被序列化
ACC_SYNTHETIC 0x1000 由编译器自动生成
ACC_ENUM 0x4000 enum,字段为枚举类型

3、Method 的 access_flag 取值

标志名 标志值 标志含义
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 该方法带有变长参数
ACC_NATIVE 0x0100 native
ACC_ABSTRACT 0x0400 abstract
ACC_STRICT 0x0800 strictfp
AACC_SYNTHETIC 0x1000 方法由编译器生成

六、属性

attribute_info 的数据结构伪代码如下所示:

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

1、attribute_name_index

  1. ConstantValue:仅出现在 filed_info 中,描述常量成员域的值,通知虚拟机自动为静态变量赋值。对于非 static 类型的变量(也就是实例变量)的赋值是在实例构造器方法中进行的;而对 于类变量,则有两种方式可以选择:在类构造器方法中或者使用 ConstantValue 属性。如果变量没有被 final 修饰,或者并非基本类型及字 符串,则将会选择在方法中进行初始化。
  2. Code:仅出现 method_info 中,描述函数内容,即该函数内容编译后得到的虚拟机指令,try/catch 语句对应的异常处理表等等。
  3. StackMapTable:在 JDK 1.6 发布后增加到了 Class 文件规范中,它是一个复杂的变长属性。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,目的在于代替以前比较消耗性能的基于数据流 分析的类型推导验证器。它省略了在运行期通过数据流分析去确认字节码的行为逻辑合法性的步骤,而是在编译阶 段将一系列的验证类型(Verification Types)直接记录在 Class 文件之中,通过检查这些验证类型代替了类型推导过程,从而大幅提升了字节码验证的性能。这个验证器在 JDK 1.6 中首次提供,并在 JDK 1.7 中强制代替原本基于类型推断的字节码验证器。StackMapTable 属性中包含零至多个栈映射帧(Stack Map Frames),其中的类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
  4. Exceptions:当函数抛出异常或错误时,method_info 将会保存此属性。
  5. InnerClasses:用于记录内部类与宿主类之间的关联。
  6. EnclosingMethod
  7. Synthetic:标识方法或字段为编译器自动生成的。
  8. Signature:JDK 1.5 中新增的属性,用于支持泛型情况下的方法签名,由于 Java 的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息。
  9. SourceFile:包含一个指向 Utf8 常量项的索引,即 Class 对应的源码文件名。
  10. SourceDebugExtension:用于存储额外的调试信息。
  11. LineNumberTable:Java 源码的行号与字节码指令的对应关系。
  12. LocalVariableTable:局部变量数组/本地变量表,用于保存变量名,变量定义所在行。
  13. LocalVariableTypeTable:JDK 1.5 中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加。
  14. Deprecated
  15. RuntimeVisibleAnnotations
  16. RuntimeInvisibleAnnotations
  17. RuntimeVisibleParameterAnnotations
  18. RuntimeInvisibleParameterAnnotations
  19. AnnotationDefault
  20. BootstrapMethods:JDK 1.7中新增的属性,用于保存 invokedynamic 指令引用的引导方法限定符。切记,类文件的属性表中最多也只能有一个 BootstrapMethods 属性。

**LineNumberTable **

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

1、start_pc:为 code[] 数组元素的索引,用于指向 Code_attribute 中 code 数组某处指令。
2、line_number:为 start_pc 对应源文件代码的行号。需要注意的是,多个 line_number_table 元素可以指向同一行代码,因为一行 Java 代码很可能被编译成多条指令。

**LocalVariableTable **

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}

其中最重要的元素是 local_variable_table 数组,其中的 start_pc 与 length 这两个参数
决定了一个局部变量在 code 数组中的有效范围。
需要注意的是,每个非 static 函数都会自动创建一个叫做 this 的本地变量,代表当前是在哪个对象上调用此函数。并且,this 对象是位于局部变量数组第1个位置(即 Slot = 0),它的作用范围是贯穿整个函数的。
此外,在 JDK 1.5 引入泛型之后,LocalVariableTable 属性增加了一个 “姐妹属性”: LocalVariableTypeTable,这个新增的属性结构与 LocalVariableTable 非常相似,仅仅是把记录 的字段描述符的 descriptor_index 替换成了字段的特征签名(Signature),对于非泛型类型来 说,描述符和特征签名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉,描述符就不能准确地描述泛型类型了,因此出现了 LocalVariableTypeTable。

2、Code_attribute

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];
}
上一篇下一篇

猜你喜欢

热点阅读