JVM调优实战:二、Class的文件结构

什么是Class文件?

Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在class文件中,中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序。
Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。

无符号数

无符号数是基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以“_info”结尾。表主要用于描述有层次关系的复合结构的数据,比如方法、字段。需要注意的是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];  

}

在class文件中,主要包括魔数Class文件的版本号常量池访问标志类索引(还包括父类索引和接口索引集合)字段表集合方法表集合属性表集合
在IDEA IDE下有一个插件可以很方便的查看class文件的结构,这个插件是jclasslib Bytecode viewer。在IDEA插件管理里面搜索安装即可,关于使用的方式,使用IDE build项目后,选择对应的java类在菜单点击view–>Show Bytecode With Jclasslib菜单即可查看。

Class的文件结构

魔数

位于class文件的头4个字节,唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件,Class文件魔数的值为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。
很多文件存储标准中都使用魔数来进行身份识别,比如图片格式,如gif或jpeg等在文件头中都存有魔数。使用魔术而不是使用扩展名是基于安全性考虑的,扩展名可以随意被改变。

Class文件的版本号

紧跟在魔数的后面的4个字节是Class文件的版本号,这个版本号分为两部分
1.前2个字节表示次版本号(minor_version)
2.后2个字节表示主版本号(major_version)
这个版本号随着JDK版本的不同而不同,JDK1.8编译出来的class文件主版本号是52,次版本号是0

常量池

按顺序排列下来,继魔数、版本号之后就是常量池入口,这是class文件结构中最复杂的部分。
常量池简单理解就是class文件的资源库:
1.常量池是Class文件结构中与其它项目关联最多的数据类型
2.常量池是占用Class文件空间最大的数据项目之一
3.是在文件中第一个出现的表类型数据项目
首先常量池的入口是一个U2(2个字节)的数据类型,表示常量池中常量表的个数,从1开始计数,在class文件结构中只有常量池的容量是从1开始计数的,第0位意味不引用任何一个常量池项目。
常量池主要存储字面量以及符号引用
1.字面量: 比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
2.符号引用: 属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
    Java代码在进行Java编译的时候,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法和字段的最终内存布局信息,因此这些字段和方法的符号引用不经过转换的话是无法被虚拟机使用的。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析并翻译到具体的内存地址之中。
    constant_pool_count:占2字节,本例为0x0016,转化为十进制为22,即说明常量池中有21个常量
    constant_pool:表类型数据集合,即常量池中每一项常量都是一个表,共有14种(JDK1.7前只有11种)结构各不相同的表结构数据。这14种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型。

访问标志(2字节)

在常量池这一大块内容后面紧跟着的则是访问标志(Access flags),这个标志主要用于识别类或者接口层次的访问信息,主要包括:

  • 是否final
  • 是否public,否则是private
  • 是否是接口
  • 是否可用invokespecial字节码指令
  • 是否是abstract
  • 是否是注解
  • 是否是枚举
    access_flags一共有16个标志位可以使用,当前只定义了其中8个(JDK1.5增加后面3种),没有使用到标志位一律为0。

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

访问标志之后就是当前类索引、父类索引、接口索引集合,这三项数据主要用于确定这个类的继承关系。
其中类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引(interface)集合是一组u2类型的数据。(多实现单继承)

字段表集合

再往下就是字段表集合了,字段表(filed_info)用于描述接口或者类中声明的变量。
field_info {

u2             access_flags;                  // 修饰符标记位
u2             name_index;                    // 代表字段的简单名称,占2字节,是一个对常量池的引用 
u2             descriptor_index;              // 代表字段的类型,占2个字节,是一个对常量池的引用
u2             attributes_count;              // 属性计数器
attribute_info attributes[attributes_count];  // 属性表集合

}
字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息。
字段表集合中不会列出从父类或父接口中继承的字段,但是可能列出原本Java代码之中不存在的字段,如:内部类为了保持对外部类的访问性,自动添加指向外部类实例的字段。Java语言中字段是不能重载的,2个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的。

方法表集合

属性表之后,跟着的就是方法表了,起始u2类型数据表示方法数量,然后是方法表集合。Class文件存储格式对方法的描述采取了和字段描述集合完全一致的方式。
method_info {

u2             access_flags;
u2             name_index;
u2             descriptor_index;
u2             attributes_count;
attribute_info attributes[attributes_count];

}

属性表集合

最后是属性表集合,与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性。

属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量值
Deprecated类文件、字段表、方法表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常
InnerClasses类文件内部类列表
LineNumberTaleCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述(局部变量作用域)
SourceFile类文件源文件名称
Synthetic类文件、方法表、字段表标识方法或字段是由编译器自动生成的
  • Code

Java程序方法体中的代码经过Javac编译处理后,最终变为字节码指令存储在Code属性内。
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 属性名索引,常量值固定为"Code"

attribute_length 属性值长度,值为整个表的长度减去6个字节

max_stack 操作数栈深度最大值

max_locals 局部变量表所需的存储空间,单位为"Slot",Slot是虚拟机为局部变量分配内存所使用的最小的单位。

code_length、code[] 存储Java源程序编译后生成的字节码指令,每个指令为u1类型的单字节。虚拟机规范中明确限制了一个方法不允许超过65535条字节指令,实际上只用了u2长度。在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量this,局部变量表中也会预留出第一个Slot来存放对象实例的引用。但如果方法为static,局部变量表长度则可能为0。

exception_table_length、exception_table[] 异常表包含四个字段。表示:如果当字节码在start_pc行到end_pc行出现类类型为catch_type或者其子类异常,则转到第handle_pc行继续处理。异常表是Java代码的一部分,编译器使用异常表而不是简答的跳转命令来实现Java异常及finally处理机制。

  • LineNumberTable
    描述Java源码行号与字节码行号(字节码偏移量)之间的对应关系。
    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];
    }

  • Exceptions
    列举出方法中可能抛出的异常
    Exceptions_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_exceptions;
    u2 exception_index_table[number_of_exceptions];
    }

  • LocalVariableTable
    描述栈中局部变量表中的变量与Java源码中定义的变量之间的关系。
    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];
    }

  • ConstantValue
    通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才具有这个属性。对于非static类型的变量的赋值是在实例构造器中进行;而对于类变量,则有两种方式:在类构造器方法中或者使用ConstantValue属性。
    ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
    }

  • Deprecated与Synthetic

Deprecated与Synthetic两个属性都属于标志类型的布尔属性,只存在有和没有区别,没有属性值区别。

  • Deprecated
    表示某个类、字段或方法已经被开发者定位不再推荐使用。使用@deprecated注释设置。
    Deprecated_attribute {

    u2 attribute_name_index;
    u4 attribute_length;
    }

  • Synthetic
    表示此字段或方法不是Java源码直接产生,而是由编译器添加。
    Synthetic_attribute {

    u2 attribute_name_index;
    u4 attribute_length;
    }

  • StackMapTable
    在JDK 1.6加入到Class文件规范中,复杂的变长属性,位于Code属性的属性表中。
    会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的是代替以前比较消耗性能的基于数据流分析的类型推导验证器。
    StackMapTable_attribute {

    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_entries;
    stack_map_frame entries[number_of_entries];
    }

  • Signature
    JDK 1.5版本加入到Class文件规范中,可选的定长属性,出现在类、字段表和方法表结构的属性表中。因为Java语言的泛型采用的是擦除法实现的伪泛型,在字节码(Code属性)中,泛型信息编译之后通通被擦除掉,如运行期做反射时无法获得泛型信息。Signature属性就是为了弥补这个缺陷而增设,可以让Java的反射API能够获取反省类型。
    Signature_attribute {

    u2 attribute_name_index;
    u4 attribute_length;
    u2 signature_index;
    }

  • BootstrapMethod
    JDK 1.7版本加入到Class文件规范中,复杂的可变属性,位于类文件的属性表中。用于记录invokeddynamic指令引用的引导方法限定符,最多只能有一个BootstrapMethod属性。

BootstrapMethods_attribute {

u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{   u2 bootstrap_method_ref;
    u2 num_bootstrap_arguments;
    u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];

}

  • InnerClass
    用于记录内部类与宿主类之间的关联。
    InnerClasses_attribute {

    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    { u2 inner_class_info_index;
    u2 outer_class_info_index;
    u2 inner_name_index;
    u2 inner_class_access_flags;
    } classes[number_of_classes];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Seven的代码实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值