Class类文件结构

Class类文件结构

class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。

根据java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类型为基础。

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

表:是由多个无符号数或者其他表作为数据项构成的复合数据类型。表用于描述由层次关系的复合结构的数据,整个Class文件本质上就是一张表。

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attribute_count 1
attribute_info attributes attributes_count
    public class Test {
        private int m;

        private int incr() {
            return m + 1;
        }

    }
    
Class类文件结构_第1张图片
image

魔术与Class文件的版本号

每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都是用魔数来进行身份识别。
Class文件的魔数的获得很有“浪漫气息”,值为:0xCAFEBABE。

紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1(JDK1.0–1.1使用了45.0–45.3的版本号),高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即时文件格式并未发生变化。

常量池

紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型的数据项目。

由于常量池中常量数量不固定,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。这个容量计数是从1而不是0开始的。这样做是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的意思,这种情况就可以把索引值置为0来表示

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
而符号引用则属于编译原理方面的概念,包括下面三类常量:

类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符

常量池中每一项常量都是一个表,共有14种结构各不相同的表结构数据,这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型,14种常量类型所代表的具体含义如下表:

类型 标志 含义
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
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_InterfaceMethod_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

14种常量类型各自均有自己的结构。







































































































































常量

项目

类型

含义

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



tag



U1



5



bytes



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



index



U2



指向声明字段的类或接口描述符CONSTANT_Class_info的索引项



index



U2



指向字段描述符CONSTANT_NameAndType_info的索引项



CONSTANT_Methodref_info



tag



U1



10



index



U2



指向声明方法的类描述符CONSTANT_Class_info的索引项



index



U2



指向名称及类描述符CONSTANT_NameAndType_info的索引项



CONSTANT_InterfaceMethod_info



tag



U1



11



index



U2



指向声明方法的接口描述符COSNTANT_Class_info的索引项



index



U2



指向名称及类描述符CONSTANT_NameAndType_info的索引项



CONSTANT_NameAndType_info



tag



U1



12



index



U2



指向该字段或方法名称常量池的索引



index



U2



指向该字段或方法描述符常量池的索引



CONSTANT_MethodHandle_info



tag



U1



15



reference_kind



U2



值必须在1-9之间,决定了方法句柄的类型,方法句柄累心的值表示方法句柄的字节码行为



reference_ index



U2



值必须是对常量池的有效索引



CONSTANT_MethodType_info



tag



U1



16



descriptor_index



U2



值必须是对常量池的有效索引,常量池在改索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符



CONSTANT_InvokeDynamic_info



tag



U1



18



bootstrap_method_attrindex



U2



值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引



name_and_type_index



U2



值必须是对当前常量池的有效索引,常量池在该索引处的项必须是COSTANT_NameAndType_info结构,表示方法名和方法描述符


类或者接口层次的访问信息

access_flag
在常量池结束之后,紧接着的2个字节代表访问标志(access_flag),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口,是否定义为public类型,abstract类型,如果是类的话,是否声明为final,等等。每种访问信息都由一个十六进制的标志值表示,如果同时具有多种访问信息,则得到的标志值为这几种访问信息的标志值的逻辑或。





























标志名称



标志值



含义



ACC_PUBLIC



0x0001



是否是public



ACC_FINAL



0x0010



是否被声明为final,只有类可以设置



ACC_SUPER



0x0020



是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真



ACC_INTERFACE



0x0200



标识是一个接口



ACC_ABSTRACT



0x0400



是否是abstract,对于接口和抽象类来说为真,其他类都为假



ACC_SYNITHETIC



0x1000



标识这个类并非由用户代码产生



ACC_ANNOTATION



0x2000



标识这是一个注解



ACC_ENUM



0x4000



标识这是一个枚举类


类索引、父类索引与接口索引集合

由于Java中是单继承,所以父类索引只有一个;但Java类可以实现多个接口,所以接口索引是一个集合。

字段表集合

字段表用来描述接口或类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内变量。

类型 名称 数量
U2 access_flags 1
U2 name_index 1
U2 descriptor_index 1
U2 attributes_count 1
attribute_info attributes attributes_count

字段标志:
































标志名称



标志值



含义



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


字段描述类型

标识字符 含义
B byte
J long
C char
S short
D double
Z boolean
F float
V void
I int
L 对象类型,如Ljava/lang/Object

字段表集合中不会列出从父类或接口中继承来的字段,但有可能会出现原本Java程序中没有的字段。比较典型的例子是内部类,为了在内部类中保持对外部类的访问性,会增加一个指向外部类实例的字段。另外,在Java语言中字段无法重载,也就是字段名不能重复,即使两个字段的数据类型、修饰符都不相同。不过对于字节码来说,如果两个字段的描述符不一致,那么就可以有重复的字段名。

方法表

方法表结构与字段表结构几乎相同,仅在访问标志和属性表集合的可选项中有所区别。方法里的java代码存放在方法属性表中名为Code的属性中。
当描述符用来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号"()"内。比如方法void inc()的描述符是:()V。方法java.lang.String toString()的描述符是:()Ljava/lang/String。方法int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)的描述符是:([CII[CIII)I。
(扩展网上查的:

Java在编译之后会在字节码文件中生成方法,称之为实例构造器,该实例构造器会将语句块,变量初始化,调用父类的构造器等操作收敛到方法中,收敛顺序(这里只讨论非静态变量和语句块)为:

  1. 父类非静态变量初始化
  2. 父类非静态代码块
  3. 父类构造函数
  4. 子类非静态变量初始化
  5. 子类非静态代码块
  6. 子类构造函数
    所谓收敛到方法中的意思就是,将这些操作放入到中去执行


Java在编译之后会在字节码文件中生成方法,称之为类构造器方法,在初始化阶段执行,会将静态语句块,静态变量初始化,收敛到方法中,收敛顺序为:

  1. 父类静态变量初始化
  2. 父类静态代码块
  3. 子类静态变量初始化
  4. 子类静态代码块

方法是在类加载过程中执行的,而是在对象实例化执行的,所以一定比先执行。所以整个顺序就是:

  1. 父类静态变量初始化
  2. 父类静态代码块
  3. 子类静态变量初始化
  4. 子类静态代码块
  5. 父类变量初始化
  6. 父类代码块
  7. 父类构造函数
  8. 子类变量初始化
  9. 子类代码块
  10. 子类构造函数

属性表

属性表在前面出现了多次,在class文件、字段表和方法表都可以携带自己的属性表集合,来描述某些场景专有的信息。
属性表集合存在的位置也是不确定的,不仅可以存储在class文件结尾处,还可以作为数据项存在于类、方法表集合和字段表集合中。

常见的属性:






















































































属性名称



使用位置



含义



Since



Code



方法表



Java代码编译后的字节码指令



Java虚拟机规范(第二版)》



ConstantValue



字段表



final关键字定义的常量值



Java虚拟机规范(第二版)》



Deprecated



类、方法表、字段表



被声明为deprecated的方法和字段



Java虚拟机规范(第二版)》



Exception



方法表



方法抛出的异常



Java虚拟机规范(第二版)》



EnclosingMethod



类文件



仅当一个类为局部类或匿名类时才能拥有这个属性,来标识这个类所在的外围方法



Java虚拟机规范(第二版)》



InnerClasses



类文件



内部类列表



Java虚拟机规范(第二版)》



LineNumberTable



Code属性



Java源码的行号与字节码指令的对应关系



Java虚拟机规范(第二版)》



LocalVariableTable



Code属性



方法的局部变量描述



Java虚拟机规范(第二版)》



StackMapTable



Code属性



供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型检查



JDK 1.6



Signature



类、方法表、字段表



用于保存泛型中的类型信息



JDK 1.5



SourceFile



类文件



记录源文件名称



Java虚拟机规范(第二版)》



SourceDebugExtension



类文件



用于存储额外的调试信息



Java虚拟机规范(第二版)》



Synthetic



类、方法表、字段表



标识方法或字段为编译器自动生成



Java虚拟机规范(第二版)》



LocalVariableTypeTable





使用特征签名代替描述符,为了描述泛型参数化类型



JDK 1.5



RuntimeVisibleAnnotations



类、方法表、字段表



为动态注解提供支持



JDK 1.5



RuntimeInvisibleAnnotations



类、方法表、字段表



RuntimeVisibleAnnotations作用相反



JDK 1.5



RuntimeVisbleParameterAnnotations



方法表



RuntimeVisibleAnnotations类似



JDK 1.5



RuntimeInvisbleParameterAnnotations



方法表



RuntimeInvisibleAnnotations类似



JDK 1.5



AnnotationDefault



方法表



用于记录注解类元素的默认值



JDK 1.5



BootstrapMethods



类文件



用于保存invokedynamic指令引用的引导方法限定符



JDK 1.7


由于属性表集合的限制较小,每个属性都会有自己的格式,因此class文件对于属性的格式要求也比较宽松,只需要满足一些特定的条件即可。下表是属性的结构:
















类型



名称



数量



U2



attribute_name_index



1



U4



attribute_length



1



U1



info



attribute_length


(1)Code属性


最常用的属性恐怕就是Code属性了,因为大多数的方法都会有编译后的字节码指令,这些指令就存储在方法表中的Code属性中。如果一个Java程序的信息可以分为代码(方法体中的代码)和元数据(包括类、字段、方法定义以及其它信息),那么Code属性存储的就是代码,其它所有的结构存储的都是元数据。不过并非所有的方法表都有这个Code属性,比如接口或抽象类中的方法表就不存在Code属性(JDK 1.8中的接口也可以定义方法了)。Code属性的结构如下:





































类型



名称



数量



U2



attribute_name_index



1



U4



attribute_lenght



1



U2



max_stack



1



U2



max_locals



1



U4



code_length



1



U1



code



code_length



U2



exception_table_length



1



exception_info



exception_table



exception_table_length



U2



attributes_count



1



attribute_info



attributes



attributes_count



(2)LinNumberTable

(3)LocalVariableTable
(4) SourceFile

你可能感兴趣的:(Class类文件结构)