• 实战java虚拟机(四)——Class文件结构


    前言


     对于Java虚拟机来说,Class文件是虚拟机的一个重要接口。无论使用何种语言开发,只要能将源文件编译成正确的Class文件,那么这种语言就可以在Java虚拟机上运行。

    Class文件总体结构如下图所示

    Java虚拟机规范中,Class文件使用一种类似于C语言结构体的方式进行描述,并且统一使用无符号整数作为基本数据类型,由u1,u2,u4,u8分别表示无符号单字节,2字节,4字节和8字节整数,对于字符串,使用u1数组进行表示。

    因此,一个Class文件可以非常严谨地被描述成:

    ClassFile{
        u4        magic;
        u2        minor_version;
        u2        major_version;
        u2        constant_pool_count;
        cp_info            constant_pool[constant_pool_count];
        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];
    }

    Class文件的结构严格按照该结构体的定义:(以二进制格式打开Class文件分析)

    (1) 文件以一个4字节的Magic(魔数)开头,紧跟着大、小版本号。

    (2) 在版本号之后是常量池,常量池的个数为constant_pool_count,常量池中的表项有constant_pool_count -1 项。

    (3) 常量池之后是类的访问修饰符、代表自身类的引用、父类引用及接口数量和实现的接口引用。

    (4) 在接口之后,有字段的数量和字段描述、方法数量及方法的描述。

    (5) 存放类文件的属性信息

     

    一、魔数


     魔数作为Class文件的标志,用来告诉Java虚拟机,这是一个Class文件。它是4字节的无符号整数,固定为0xCAFEBABE

    当一个文件不以0xCAFEBABE开头,则虚拟机进行文件校验时就会抛出以下错误:

    Exception in thread “main” java.lang.ClassFormatError: Incompatible magic value 184466110 in class file ***

     

    二、 版本


     魔数后面紧跟的是小版本号与大版本号,首先是小版本号,然后是大版本号,它们都是2字节的无符号整数

    Class文件的版本号与Java编译器的对应关系如下表

    大版本号

    小版本号

    编译器版本

    46

    0

    1.2

    47

    0

    1.3

    48

    0

    1.4

    49

    0

    1.5

    50

    0

    1.6

    51

    0

    1.7

    52

    0

    1.8

    53

    0

    1.9

    54

    0

    10

     

    这是从我项目中随机取的一个class文件,通过观察第一行魔数后面的4个字节可知,0x34 = 52 十进制,因此是1.8版本编译的

     

    三、常量池


     常量池是Class文件内容最丰富的区域之一,它对于Class文件中字段和方法的解析也有至关重要的作用,版本号之后紧跟的是常量池的数量,以及若干个常量池表项

    常量池表项的类型及其TAG值如下表

    常量池类型

    TAG

    CONSTANT_Class

    7

    CONSTANT_Methodref

    10

    CONSTANT_String

    8

    CONSTANT_Float

    4

    CONSTANT_Double

    6

    CONSTANT_Utf8

    1

    CONSTANT_MethodType

    16

    CONSTANT_Fieldref

    9

    CONSTANT_InterfaceMethodref

    11

    CONSTANT_Integer

    3

    CONSTANT_Long

    5

    CONSTANT_NameAndType

    12

    CONSTANT_MethodHandle

    15

    CONSTANT_InvokeDynamic

    18

    继续使用上一个class的内容,第一行89列可以看出,0x15 = 21,该文件中的常量池表项有21-1=20项(常量池0位空缺项,不存放实际内容)。数量之后就是常量池实际内容。

     

    四、Class的访问标记


     常量池后紧跟的是访问标记,用2字节表示,用于表明类的访问信息,如publicfinalabstract

    Access Flag的标记位和含义

     

    标记名称

    数值

    描述

    ACC_PUBLIC

    0x0001

    表示public

    ACC_FINAL

    0x0010

    final

    ACC_SUPER

    0x0020

    使用增强的方法调用父类方法

    ACC_INTERFACE

    0x0200

    接口

    ACC_ABSTRACT

    0x0400

    抽象类

    ACC_SYNTHETIC

    0x1000

    由编译器产生的类,没有源码对应

    ACC_ANNOTATION

    0x2000

    注释

    ACC_ENUM

    0x4000

    枚举

    (表中数值为了容易描述而取的特殊值)

    比如,0x0021表示该类为publicACC_SUPER标记置为1

    写一个简单的类

    编译后部分class文件如下

    0x0421表明该类是抽象类,且为publicACC_SUPER置为1(一般都会为1,继承object类)

     

    五、当前类、父类和接口


     在访问标记后,会指定该类的类别、父类类别和实现的接口,格式如下:

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

    其中this_classsuper_class都是2字节无符号整数,指向常量池一个CONSTANT_Class,表示当前类型和父类,由于可以继承多个接口,因此,需要用数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class

     

    继续使用该class,其中00 02 00 03表明的是本类和父类在常量池的索引,后面的 00 00 表明继承接口数量为0,因此interfaces数组没有数据

     

     六、Class文件的字段


     接口描述后,会有类的字段信息

    u2            fields_count
    
    field_info   fields[fields_count]

    字段数量fields_count是一个2字节无符号整数,field_info是字段具体信息,结构如下:

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

    字段Access Flag的标记位及含义

    标记名称

    取值

    描述

    ACC_PUBLIC

    0x0001

    表示public字段

    ACC_PRIVATE

    0x0002

    表示private字段

    ACC_PROTECTED

    0x0004

    表示protected字段

    ACC_STATIC

    0x0008

    表示静态字段

    ACC_FINAL

    0x0010

    表示final字段

    ACC_VOLATILE

    0x0040

    表示volatile字段

    ACC_TRANSIENT

    0x0080

    表示瞬时字段,在持久化读写时忽略该字段

    ACC_SYNTHETIC

    0x1000

    由编译器产生的方法,没有源码对应

    ACC_ENUM

    0x4000

    枚举

     

     

     

    继续使用上面的例子,从offset0000027210列开始,00 01表明的是字段数量,该类中数量为1,后续00 19表明这个字段是public static final字段,接下来00 04为常量池索引,表示字段名称,常量池第四个为a,即字段名称为a00 05为字段类型描述,常量池第5java/lang/String,表示为String类型,接着是属性数量,00 01表示字段存在一个属性, 后面的00 06为属性名,常量池第6项为ConstantValue,表示该属性为常量属性。

    常量属性的结构为

    ConstantValue_attribute{
        u2 attribute_name_index;
        u4 attribute_length;
        u2 constantvalue_index;
    }

    之后连续4字节为属性剩余长度,00 00 00 02,表示从0x00000002之后的2字节为属性全部内容,00 07即常量池第7项,得到常量CONSTANT_String,值为空。所以分析出该常量字段为public static final String a = “”;

    七、class文件的方法基本结构


    在字段之后,就是类的方法信息,它由两部分组成

    u2 methods_count;
    method_info methods[methods_count];

    每个method_info表示一个方法,结构如下

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

    方法访问标记取值

    标记名称

    作用

    ACC_PUBLIC

    0x0001

    public

    ACC_PRIVATE

    0x0002

    private

    ACC_PROTECTED

    0x0004

    protected

    ACC_STATIC

    0x0008

    静态方法

    ACC_FINAL

    0x0010

    final方法不可被重载重写

    ACC_SYNCHRONIZED

    0x0020

    同步方法

    ACC_BRIDGE

    0x0040

    由编译器产生的桥接方法

    ACC_VARARGS

    0x0080

    可变参数方法

    ACC_NATIVE

    0x0100

    native

    ACC_ABSTRACT

    0x0400

    抽象方法

    ACC_STRICT

    0x0800

    浮点模式为FP-strict

    ACC_SYNTHETIC

    0x1000

    编译器产生的方法,没有源码对应

    访问标记name_index表示方法名称,是指向常量池的索引

    descriptor_index为方法描述符,也是指向常量池的索引,表示方法的签名(参数、返回值等)

    后面的attributes_countattribute_info表示属性数量及描述

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

    常用属性

    属性

    作用

    ConstantValue

    字段常量

    Code

    表示方法的字节码

    StackMapTable

    Code属性的描述属性,用于字节码变量类型验证

    Exceptions

    方法的异常信息

    SourceFile

    类文件的属性,表示生成这个类的源码

    LineNumberTable

    Code属性的描述属性,描述行号和字节码的对应关系

    LocalVariableTable

    Code属性的描述属性,描述函数局部变量表

    BootstrapMethods

    类文件的描述属性,存放类的引导方法,用于invokeDynamic

    StackMapTable

    Code属性的描述属性,用于字节码类型校验

    Code属性较为复杂,整理一下作用与结构成下图

    其中对于Class还有InnerClasses属性和Deprecated属性,比较少用到这里就不多说。

    八、Class文件总结


     Class文件是非常庞大的,这里我只摘抄一些比较重要的点做一些笔记,后续随着Java平台的发展,Class文件也会有很多补充,但是基本的结构和文件格式应该是不会做重大调整的。从Java虚拟机的角度看,通过Class文件可以让更多的计算机语言支持Java虚拟机平台,Class文件不仅仅是Java虚拟机的执行入口,更是Java生态圈的基础和核心

     

  • 相关阅读:
    数据结构——霍夫曼树及题目场景应用
    算法——模式匹配
    深入理解Java虚拟机(十)——线程安全与锁优化
    深入理解Java虚拟机(九)——后端编译与优化
    算法——计算点集中共线最多点的直线
    算法——移掉K位数字使得数值最小
    算法—— n个骰子的点数
    Java并发编程的艺术(十二)——并发容器和框架
    算法——不用加减乘除符号运算加法
    5章-项目范围管理-day4
  • 原文地址:https://www.cnblogs.com/gtblog/p/11643428.html
Copyright © 2020-2023  润新知