Class文件笔记

一、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];
}
  • attribute_name_index:为 CONSTANT_Utf8 类型常量项的索引,表示属性的名称。
  • attribute_length:属性的长度。
  • info:属性具体的内容。

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];
}
  • attribute_name_index、attribute_length:attribute_length 的值为整个 Code 属性减去 attribute_name_index 和 attribute_length 的长度。
  • max_stack:为当前方法执行时的最大栈深度,所以 JVM 在执行方法时,线程栈的栈帧(操作数栈,operand satck)大小是可以提前知道的。每一个函数执行的时候都会分配一个操作数栈和局部变量数组,而 Code_attribure 需要包含它们,以便 JVM 在执行函数前就可以分配相应的空间。
  • max_locals:为当前方法分配的局部变量个数,包括调用方式时传递的参数。long 和 double 类型计数为 2,其他为 1。max_locals 的单位是 Slot,Slot 是
    虚拟机为局部变量分配内存所使用的最小单位。局部变量表中的 Slot 可以重用,当代码执行超出一个局部变量的作用域时,这个局部变量 所占的 Slot 可以被其他局部变量所使用,Javac 编译器会根据变量的作用域来分配 Slot 给各个 变量使用,然后计算出 max_locals 的大小。
  • code_length:为方法编译后的字节码的长度。
  • code:用于存储字节码指令的一系列字节流。既然叫字节码指令,那么每个指令就是一个 u1 类型的单字节。一个 u1 数据类型的取值范围为 0x00~0xFF,对应十进制的 0~255,也就是一共可以表达 256 条指令。
  • exception_table_length:表示 exception_table 的长度。
  • exception_table:每个成员为一个 ExceptionHandler,并且一个函数可以包含多个 try/catch 语句,一个 try/catch 语句对应 exception_table 数组中的一项。
  • start_pc、end_pc:为异常处理字节码在 code[] 的索引值。当程序计数器在 [start_pc, end_pc) 内时,表示异常会被该 ExceptionHandler 捕获。
  • handler_pc:表示 ExceptionHandler 的起点,为 code[] 的索引值。
  • catch_type:为 CONSTANT_Class 类型常量项的索引,表示处理的异常类型。如果该值为 0,则该 ExceptionHandler 会在所有异常抛出时会被执行,可以用来实现 finally 代码。当 catch_type 的值为 0 时,代表任意异常情况都需要转向到 handler_pc 处进行处理。此外,编译器使用异常表而不是简单的跳转命令来实现 Java 异常及 finally 处理机制。
  • attributes_count 和 attributes:表示该 exception_table 拥有的 attribute 数量与数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容