类文件结构(四)

Class文件是一组以8位字节为基础单位的二进制流,各个数据项严格按照顺序紧凑地排列在Class文件中,中间没有任何分隔符,整个Class文件存储的内容几乎全是程序运行的必要数据,没有空隙存在。当遇到8位字节以上空间的数据项时,则按照高位在前的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范,Class文件格式采用一种类似c语言结构体的伪结构来存储数据,这种伪结构中只有两种数据结构:无符号数和表,后面的解析都以这两种数据类型为基础。

字节码查看工具下载

  • 无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表一个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
  • 表:是由多个符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以 “ _info ” 结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质就是一张表。
类型名称数量解释
u4magic1魔数
u2minor_version1次版本号
u2major_version1主版本号
u2constant_pool_count1常量池常量个数
cp_infoconstant_poolconstant_pool_count - 1常量池
u2access_flags1访问标识
u2this_class1类索引
u2super_class1父类索引
u2interface_count1接口数量
u2interfacesinterface_count接口内容
u2fields_count1字段表字段数量
field_infofieldsfields_count字段表
u2methods_count1方法表方法数量
method_infomethodsmethods_count方法表
u2attributes_count1属性表属性数量
attribute_infoattributesattributes_count属性表

上面的表其实可以划分为以下七个部分,这七个部分组成了一个完整的 Class 字节码文件:

  • 魔数与Class文件版本

每个Class的头4个字节成为魔数,唯一作用就是确定class文件时被虚拟机接受的(0xCAFEBABE),紧接着是版本号第5、6字节是次版本号,第7、8字节是主版本号,我们写一段程序编译成class来解释:

public class TestClass{
    private int m;
    public int inc(){
        return m+1;
    }
}

这里写图片描述

如图是用16进制编辑器查看,开头是0xCAFEBABE,次版本号0x0000,主版本号0x0034(即十进制52),

  • 常量池

紧接着主版本号之后的是常量池入口,它是class文件中与其他项目关联最多的数据类型,也是class文件空间最大的数据项目之一,还是在class文件中第一个出现的表类型数据项目。
紧接着主版本号的u2类型 0x0013 表示常量池常量的个数(constant_pool_count),那么紧跟着就有 constant_pool_count - 1 个常量(从索引1开始,0号常量空出来代表不指向的时候考虑),使用javap -verbose TestClass.class 查看常量池信息:

这里写图片描述

拿第一个举例说明:对应的十六进制是0A 00 04 00 0F第一个0A代表tag,代表着是一个方法表(CONSTANT_Methodref_info tag=10),00 04代表class_index(方法是哪个类的,4指向常量池的4号即Object),00 0F代表name_and_type_index,指向15号即方法名 方法签名类型()V。下面给出tag和表对应的关系:
这里写图片描述

  • 访问标志
    在常量池结束之后,紧接着的两个字节代表访问标记(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口、是否定义为public类型、是否定义为abstract类型等。具体的标志位以及标志的含义见下表
    这里写图片描述
    本例的字节码中这两个字节是 00 21,通过查看我们并没有发现有标志值是 00 21 的标志名称。这是因为这里的访问标志可能是由多个标志名称组成的,所以字节码文件中的标志值其实是多个值进行或运算的结果。
    通过查阅上述表格,我们可以知道,00 21 由 00 01 和 00 20 进行或运算得来。也就是说该类的访问标志是 public 并且允许使用 invokespecial 字节码指令的新语义。
  • 类索引、父类索引、接口索引
    类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。
    类索引。类索引用于确定这个类的全限定名,它用一个 u2 类型的数据表示。这里的类索引是 00 03 表示其指向了常量池中第 3 个常量,通过我们之前的分析,我们知道第 3 个常量其最终的信息是 TestClass类。
    父类索引。父类索引用于确定这个类的父类的全限定名,父类索引用一个u2类型的数据表示。这里的父类索引是 00 04 表示其指向了常量池中第 4 个常量,通过我们之前的分析,我们知道第 4 个常量其最终的信息是 Object 类。因为其并没有继承任何类,所以 TestClass类的父类就是默认的 Object 类。
    接口索引。接口索引集合就用来描述哪个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身就是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。对于接口索引集合,入口第一项是 u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量,而在接口计数器后则紧跟着所有的接口信息。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
    这里 TestClass类的字节码文件中,因为并没有实现任何接口,所以紧跟着父类索引后的两个字节是0x0000,这表示该类没有实现任何接口。因此后面的接口索引表为空。

  • 字段表集合
    字段表集合用于描述接口或者类中声明的变量。这里说的字段包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。
    在类接口集合后的2个字节是一个字段计数器,表示总有有几个属性字段。在字段计数器后,才是具体的属性数据。字段表的每个字段用一个名为 field_info 的表来表示,field_info 表的数据结构如下所示:
    这里写图片描述
    本例中fileds_count对应0x0001又一个字段,紧接着后面是field_info 00 02 00 05 00 06 00 00 access_flags对应0x0002查找对应表:
    这里写图片描述
    可知该字段是private修饰,name_index对应0x0005查找常量池字段名m,descript_index对应0x0006查找常量池索引6得到I,name这个I代表什么呢?字段类型?
    这里写图片描述
    可以看出对应的是int类型

  • 方法表集合
    在字段表后的 2 个字节是一个方法计数器,表示类中总有有几个方法。在字段计数器后,才是具体的方法数据。方法表中的每个方法都用一个 method_info 表示,其数据结构如下:
    这里写图片描述
    methods_count对应:0x0002代表有两个method_info,有两个方法我们就查看第二个方法inc()
    这里写图片描述
    这是一个查看字节码的可视化工具,在文章开头有链接,先看access_flag对应ox0001,查看方法访问标识表:
    这里写图片描述
    可知该方法是public,name_inde对应0x000B查找常量池可知方法名inc
    descriptor_index对应0x000C可知方法签名()I

  • 属性表集合
    属性表在前面已经出现了很多次,类文件、方法表、字段表都可以携带自己的属性表集合
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    Code属性
    Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性。
    这里写图片描述

1.attribute_name_index:指向常量池的索引,固定为Code
2.max_stack:操作数栈深度的最大值
3.max_locals:局部变量表所需的存储空间。max_locals的单位为Slot,小于32位的值(如int、byte、float)占用1一个Slot,double、long占用两个Slot。并不是方法中定义了多少个局部变量,就把这些局部变量所占Slot之和作为max_locals的值,当代码执行超过一个局部变量的作用域时,这个局部变量占用的Slot就可以被其他局部变量所使用,Javac编译器会根据变量的作用域来分配Slot给各个变量使用,然后计算max_locals的大小。
4.code_length:字节码长度
5.code:用于存储字节码指令的一系列字节流。每个指令都是一个u1类型数据,当虚拟机读取到code的一个字节码时,就可以找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及参数应当如何理解。
6.exception_table:异常表如下。如果当字节码在第start_pc行到第end_pc行之间(不含end_pc行)出现了类型为catch_type或者其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的引用),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向到handler_pc行进行处理。
这里写图片描述
异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制。

Exceptions属性
Exceptions属性:方法可能抛出的异常

LineNumberTable
Java源码行号与字节码行号之间的对应关系,当抛出异常时,行号就是从这里获取到的。

LocalVariableTable
描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。如果没有这个表,在调试期间无法获得参数值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值