Class文件结构

前言

我们常说Java是跨平台的语言,主要原因是有JVM。可以说,只要有能运行JVM的平台,Java代码基本可以无缝移植,而跨平台的基石便是Class文件。

本文旨在解析Class文件数据结构,加深对jvm的理解。

环境:

操作系统:win10

JDK版本:1.8.0_291

本文所有内容都是基于以上环境的,如果有异议的地方欢迎邮件交流。

Class类文件结构

Class文件是一组以8bit为基础的字节流,多数据项之间严格按照数据紧凑的排列在Class文件中,没有分割符。如果数据项占用超过8bit,那么以Big-Endian方式分割成若干个8bit存储。

Big-Endian见wiki

根据JVM规定,Class文件采用一种类型C结构体的伪结构体来存储数据,这种结构体中只有两种数据类型:无符号数

  • 无符号数:以u1、u2、u4、u8代表1Byte、2Byte、4Byte、8Byte的无符号数,用于描述数字、索引、数量或者按照UTF-8编码构成的字符串
  • 表:_info结尾,由多个无符号数和其他表构成的复合数据结构,用于描述有层次关系的符合数据结构

1Byte = 8bit

下面Class文件结构伪代码:

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

属性解释:

  • magic

    魔数,标识文件类型,值是0xCAFEBABE

  • minor_version,major_version

    major_version:主版本号;minor_version:次版本号。高版本JDK能向下兼容以前的版本的Class文件,不能兼容之后的,例如:JDK1.0.2能支持的版本号为45.0-45.3,不能支持45.65535版本Class文件;JDK1.1.*能支持45.0-45.65535

  • constant_pool_count

    代表常量池容量计数值,这个技术值从1开始,而不是0。假设这个值是10,那么代表常量池有9个常量,索引范围是1-9。而0则是为了满足某些指向常量池索引表达为“不引用任何常量池项”的特定情况

  • constant_pool[]

    代表实际存放常量池的数组,大小是constant_pool_count,索引范围(1-constant_pool_count-1),具体如下图:

    常量池结构

    cp_info的结构

    常量池通用结构:

    cp_info{
    u1 tag;
    u1 info[];
    }
    

    JVM虚拟机规定了不同的tag值和不同类型的字面量对应关系如下:

    Constant TypeValue涵义
    CONSTANT_Class7类或者接口的全限定名
    CONSTANT_Fieldref9表示类中的字段
    CONSTANT_Methodref10表示类中方法
    CONSTANT_InterfaceMethodref11表示类中实现的接口的方法
    CONSTANT_String8表示java.lang.String类型的常量对象
    CONSTANT_Integer3表示4字节(int)的数值常量
    CONSTANT_Float4表示4字节(float)的数值常量
    CONSTANT_Long5表示8字节(long)的数值常量
    CONSTANT_Double6表示8字节(double)的数值常量
    CONSTANT_NameAndType12表示字段或者方法的名称或者类型
    CONSTANT_Utf81表示字符串常量的值
    CONSTANT_MethodHandle15表示方法句柄
    CONSTANT_MethodType16表示方法类型
    CONSTANT_InvokeDynamic18表示invokedynamic指令所用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可选择性附加被称为静态参数(Static Arguments)的常量序列
    • CONSTANT_Class_info
    CONSTANT_Class_info{
        //值为7
        u1 tag;
        //指向一个CONSTANT_Utf8_info结构体的索引,这个结构体存储具体的值
        u2 name_index;
    }
    
    • CONSTANT_Fieldref_info,CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info
    CONSTANT_Fieldref_info {
           //值为9
        u1 tag;
        //指向CONSTANT_Class_info结构体的索引,类型可以是类也可以是接口
        u2 class_index;
        //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是字段描述符
        u2 name_and_type_index;
    }
    
    CONSTANT_Methodref_info {
           //值为10
        u1 tag;
        //指向CONSTANT_Class_info结构体的索引,类型必须是类,不可以是接口
        u2 class_index;
        //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是方法描述符;如果以'<'('\u003c')开头,一定是<init>这个特殊方法,返回值是void。
        u2 name_and_type_index;
    }
    
    CONSTANT_InterfaceMethodref_info {
           //值为11
        u1 tag;
        //指向CONSTANT_Class_info结构体的索引,类型必须是接口,不可以是类
        u2 class_index;
        //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是方法描述符
        u2 name_and_type_index;
    }
    
    • CONSTANT_String_info
    CONSTANT_String_info {
           //值为8
        u1 tag;
        //指向一个CONSTANT_Utf8_info结构体的索引,这个结构体存储具体的值
        u2 string_index;
    }
    
    • CONSTANT_Integer_info,CONSTANT_Float_info
    CONSTANT_Integer_info {
           //值为3
        u1 tag;
        //存储整数值
        u4 bytes;
    }
    
    CONSTANT_Float_info {
           //值为4
        u1 tag;
        //存储浮点值
        u4 bytes;
    }
    
    • CONSTANT_Long_info,CONSTANT_Double_info

    具体可见LONG如何存储

    CONSTANT_Long_info {
           //值为5
        u1 tag;
        //高4位
        u4 high_bytes;
        //低4位
        u4 low_bytes;
    }
    
    CONSTANT_Double_info {
           //值为6
        u1 tag;
        u4 high_bytes;
        u4 low_bytes;
    }
    
    • CONSTANT_NameAndType_info
    CONSTANT_NameAndType_info {
         	 //值为12
        u1 tag;
        //指向一个CONSTANT_Utf8_info结构体的索引,包含特殊方法<init>,或者方法和字段的非全限定名,即短名。
        u2 name_index;
        //指向一个CONSTANT_Utf8_info结构体的索引,代表字段或者方法
        u2 descriptor_index;
    }
    
    • CONSTANT_Utf8_info
    CONSTANT_Utf8_info {
             //值为12
        u1 tag;
        //长度
        u2 length;
        //字节数组
        u1 bytes[length];
    }
    
    • CONSTANT_MethodHandle_info
    CONSTANT_MethodHandle_info {
          //值为15
        u1 tag;
        //值在1-9之间,值表示方法句柄的类型及其字节码行为
        u1 reference_kind;
        //值是常量池的有效索引
        //如果reference_kind项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段创建的方法句柄
        //如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那么常量池在reference_index索引处的项必须是CONSTANT_Methodref_info结构,表示由类的方法或构造函数创建的方法句柄
        //如果reference_kind项的值是9(REF_invokeInterface),那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法创建的方法句柄
        //如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()
        //如果reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化<init>
        u2 reference_index;
    } 
    
    • CONSTANT_MethodType_info
    CONSTANT_MethodType_info {
        //值为16
       u1 tag;
       //必须是CONSTANT_Utf8_info结构,代表方法的描述符
       u2 descriptor_index;
    }
    
    • CONSTANT_InvokeDynamic_info
    // 表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。
    CONSTANT_InvokeDynamic_info { 
        //值为18
       u1 tag;
       //对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
       u2 bootstrap_method_attr_index;
       对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info 结构,表示方法名和方法描述符
       u2 name_and_type_index;
    }
    

    综上,就是1.8版本jvm字节码部分常量池所有类型,像最后三个有点抽象,暂时先放在一边,等后续我们在详谈

  • access_flags

    标志名称标志值含义适用范围
    ACC_PUBLIC0x0001声明为public,可以从包外访问所有类型
    ACC_FINAL0x0010声明为final,不允许有子类
    ACC_SUPER0x0020调用Invokescial指令对超类方法进行处理类和接口
    ACC_INTERFACE0x0200声明是接口类型而不是类接口
    ACC_ABSTRACT0x0400声明是抽象类型,不能被实例化接口或抽象类
    ACC_SYNTHETIC0x1000声明是由编译器生成的,不出现在源码中所有类型
    ACC_ANNOTATION0x2000声明为注解类型注解
    ACC_ENUM0x4000声明为枚举类型枚举
  • this_class

    指向一个CONSTANT_Class_info的常量池索引,代表该类或者接口信息

  • super_class

    如果值是0,那么代表Object类;如果不是0,值为一个指向CONSTANT_Class_info的索引,代表该类的父类

  • interfaces_count

    接口计数器,代表实现接口数量,如果没事实现接口计数器值是0

  • interfaces[]

    接口索引数组,每个索引指向一个CONSTANT_Class_info结构,代表接口信息,按照源码中从左到右排序

  • fields_count

    字段计数器,代表field_info结构的数量

  • fields[]

    field_info的集合,代表类或者接口的完整描述。注意,只包含本身类中的,不包含继承的字段。

    field_info结构

    每个字段都是由field_info结构来描述,并且一个类文件中不能有两个字段具有相同的名称和描述符

    字段描述符

    字段描述符表示类、实例或局部变量的类型

    FieldDescriptor:
        FieldType
        
    FieldType:
      BaseType
      ObjectType
      ArrayType
      
    BaseType:
      (one of)
      B C D F I J S Z
      
    ObjectType:
      L ClassName ;
      
    ArrayType:
      [ ComponentType
      
    ComponentType:
      FieldType
    

    下面的表格解释上面的作用

    FieldType术语类型解释
    Bbyte有符号的byte类型,-127-128
    Cchar基本多语言平面中的Unicode字符编码点,使用UTF-16编码
    Ddouble双精度浮点
    Ffloat单精度浮点
    Iint整数
    Jlong长整数
    L ClassName ;reference类ClassName的实例
    Sshort带符号的short类型
    Zbooleantrue or false
    [reference一维数组,二维数组用[[表示

    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_PRIVATE0x0002声明privcate,仅能在定义类中使用
    ACC_PROTECTED0x0004声明protected,可以在子类中访问
    ACC_STATIC0x0008声明static
    ACC_FINAL0x0010声明final,在对象构造之后,不能直接赋值
    ACC_VOLATILE0x0040声明volatile,不能被缓存
    ACC_TRANSIENT0x0080声明transient,不能由持久化对象管理器写入或者读取
    ACC_SYNTHETIC0x1000声明synthetic,不存在源码中,表示编译器生成
    ACC_ENUM0x4000声明为enum的元素

    name_index

    ​ 指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的非全限定名(方法、字段、局部变量和形式参数的名称存储为非限定名称)

    descriptor_index

    ​ 指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的字段描述符

    attributes_count

    ​ 表示该字段附加属性的数量

    attributes[]

    ​ 表示该字段附加属性列表,每一个项都是一个attribute_info结构,具体见后面attributes部分

  • methods_count

    methods_count项的值给出了method_info结构在方法表中的数量

  • methods[]

    method_info结构表示该类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法和任何类或接口初始化方法;不包括从父类或者父接口继承的方法。

    method_info结构

    每个方法都是有method_info结构描述,一个类文件中的两个方法不能有相同的名称和描述符

    方法描述符

    MethodDescriptor:

    ​ ( {ParameterDescriptor} ) ReturnDescriptor

    ParameterDescriptor:

    ​ FieldType

    ReturnDescriptor:

    ​ FieldType
    ​ VoidDescriptor

    VoidDescriptor:

    ​ v(表示方法不返回任何值)

    方法:Object m(int i, double d, Thread t) {…}

    方法描述符:(IDLjava/lang/Thread;)Ljava/lang/Object;

    Method_info结构:

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

    access_flags:

    ​ 表示方法的访问权限和属性,具体如下表:

    名称解释
    ACC_PUBLIC0x0001声明public,可以包外访问
    ACC_PRIVATE0x0002声明private,只有类内能访问
    ACC_PROTECTED0x0004声明protected,子类可以访问
    ACC_STATIC0x0008声明static
    ACC_FINAL0x0010声明final,不能被重写
    ACC_SYNCHRONIZED0x0020声明synchronized,使用时被monitor包裹
    ACC_BRIDGE0x0040声明一个桥接方法,由编译器生产
    ACC_VARARGS0x0080声明具有可变数量的参数。
    ACC_NATIVE0x0100声明native,用除了java的其他语言实现
    ACC_ABSTRACT0x0400声明为abstract;没有提供任何实现。
    ACC_STRICT0x0800声明strictfp,浮点模式是FP-strict
    ACC_SYNTHETIC0x1000声明synthetic,不存在源码中,表示编译器生成

    name_index

    ​ 指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的非全限定名(方法、字段、局部变量和形式参数的名称存储为非限定名称)

    descriptor_index

    指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的字段描述符

    attributes_count

    表示该方法附加属性的数量

    attributes[]

    表示该字段附加属性列表,每一个项都是一个attribute_info结构,具体见后面attributes部分

  • attributes_count

    attributes数量

  • attributes[]

    attribute_info集合,在Class文件,字段表,方法表中都可以携带自己的属性表集合,用以描述某些场景的专用信息,jdk8中一共有23项属性,具体如下:

    属性名使用位置出现版本解释
    ConstantValue字段表1.0.2final关键字定义的常量值
    Code方法表1.0.2Java方法包裹代码编译成的字节码指令,native方法没有
    StackMapTableCode属性6在类型检查的验证中使用
    Exceptions方法表1.0.2Exceptions属性指示方法可能抛出哪些已检查异常
    InnerClasses类文件1.1内部类列表
    EnclosingMethod类文件5.0当且仅当类表示局部类或匿名类时,类必须具有EnclosingMethod属性
    Synthetic类、方法表、字段表1.1标识方法或字段为编译器自动生成
    Signature类、方法表、字段表5.0支持泛型下的方法签名,任何类、接口、初始化方法或者成员的泛型签名如果包含类型变量(Type Variables)或者参数化类型(Parameterized Type),该属性记录泛型签名信息
    SourceFile类文件1.0.2记录源文件名称
    SourceDebugExtension类文件5.0存储额外的调试信息
    LineNumberTableCode属性1.0.2Java源码行号和字节码指令对应关系
    LocalVariableTableCode属性1.0.2方法的局部变量描述,调试期间用于确定局部变量的值
    LocalVariableTypeTableCode属性5.0在方法执行期间,调试器可以使用它来确定给定局部变量的值
    Deprecated类、方法表、字段表1.1标记类、接口、方法、字段被废弃
    RuntimeVisibleAnnotations类、方法表、字段表5.0运行时可见注解
    RuntimeInvisibleAnnotations类、方法表、字段表5.0运行时不可见注解
    RuntimeVisibleParameterAnnotations方法表5.0运行时可见方法参数注解
    RuntimeInvisibleParameterAnnotations方法表5.0运行时不可见方法参数注解
    RuntimeVisibleTypeAnnotations类、方法表、字段表、Code属性8运行时可见类型注解
    RuntimeInvisibleTypeAnnotations类、方法表、字段表、Code属性8运行时不可见类型注解
    AnnotationDefault方法表5.0记录注解类元素的默认值
    BootstrapMethods类文件7保存invokedynamic指令引用的引导方法说明符
    MethodParameters方法表8记录了关于方法的形式参数的信息,比如它们的名称。

以上,就是本次的全部内容。

参考文献

Oracle JVM规范