class 文件结构 —— JVM篇

一、class文件

     Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义),各个数据项目严格按照顺序紧凑的排列在Class文件中,不包含任何分隔符,使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙。当遇到需要占用超过8位字节以上空间的数据项目时,会按照高位在前的方式分割为多个8位字节进行存储。

     Java虚拟机规范规定的,class文件格式采用的类似C语言的结构体的伪结构来存储的,这种结构只有两种数据类型:

  • 无符号数:属于基本数据类型,主要用于描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值。数据类型 U1 U2 U4 U8 也只是逻辑上的区分。

    • u1 — 表示一个字节
    • u2 — 表示两个字节
    • u4 — 表示四个字节
    • u8 — 表示八个字节
  • 表:由多个无符号数或者其他表作为数据项构成的复合数据类型。所有的表都习惯以_info结尾 表主要用于描述有层次关系的复合结构数据。 比如方法、字段需要注意的是class文件没有分隔符,所以每个二进制数据类型都是严格定义的具体的顺序如下:

    image-20220108201144021

二、魔数(Magic Version)

  1. 每一个class文件的头4个字节被称为魔数 magicNumber

  2. 唯一做你哥是用于确定这个文件是否为一个能被虚拟机接受的class文件

  3. Class文件魔数值为0xCAFEBABE 如果以个文件不是以CAFEBABE开头,那么它就肯定不是java的class文件。

     那么它也是java.class的识别魔数。

image-20220108211033106

     很多的文件存储标准中都使用魔数来识别文件的身份。 譬如图片格式.gif 或 jpeg等在文件的头部都存有魔数,使用魔数而不是文件的扩展名称来判断 ,这种情况是处于安全的考虑。

三、class 文件版本号

     紧挨着魔数的4个字节表示class的文件的版本号 版本号:

1. 次版本号

     minor_version 前2个字节用于表示次版本号
例如:00 00

2. 主版本号

     major_version 后2个字节用于表示主版本号
例如: 00 34

image-20220108211128879

     这个版本号随着jdk版本的不同而表示不同版本的范围。Java的版本号是从45开始的,如果class的版本号超过虚拟机的版本就会被拒绝执行。

  • JDK1.2 ----0X002E 46

  • JDK1.3 ----0X002F 47

  • JDK1.4 ----0X0030 48

  • JDK1.5 ----0X0031 49

  • JDK1.6 ----0X0032 50

  • JDK1.7 ----0X0033 51

  • JDK1.8 ----0X0034 52

四、常量池

     CONSTANT_POOL_COUNT和CONSTANT_POOL,紧跟着魔数与版本号之后的是常量池入口,常量池简单理解为class文件的资源库。

  1. 它是class文件结构中与其他项目关联最多的数据类型

  2. 是占用class文件空间最大的数据项目之一

  3. 是在文件中第一个出现的表类型数据项目。

CONSTANT_POOL_COUNT(常量个数)

     常量池的数量是不固定,所以在常量池的入口需要放置一个u2类型的数据,代表常量池的计数值CONSTANT_POOL_COUNT。

     CONSTANT_POOL_COUNT 从1开始计数的。 class文件结构中只有常量池的容量计数是从1开始的。第0项腾出来满足后面某些指向常量池的索引值的数据,在特定的情况下需要表达“不引用任何一个常量池项目” 把索引值的第0项留给JVM自己用。

     CONSTANT_POOL是没有索引值为0的入口的,但是在CONSTANT_POOL_COUNT缺失的第0项也是要被计算在内的。比如CONSTANT_POOL 中有14项 那么CONSTANT_POOL_COUNT的数值就是15

CONSTANT_POOL(常量池表)

     常量池中主要存放两大类常量:

1.字面量: 比较接近java语言层面的常量的概念 比如 字符串 被final关键字声明的常量值。

2.符号引用: 属于编译原理方面的概念 包括三项:

  • 类和接口的全名
  • 字段的名称和描述符
  • 方法的名称和描述符

     在加载class文件的时候 是进行动态连接的。在class文件中不会保存各个方法和字段的最终内存布局信息。(需要经过转换) 当虚拟机运行时 需要从常量池获得对应的符号引用,再在类创建时或者运行时解析并翻译到具体的内存地址中。

     CONSTANT_POOL 表示的是类型数据集合,在该常量池中,每一项常量都是一个表 共有14种 -----JDK1.7版本,这14种结构的表都是不相同的结构数据。14个表都有一共同的特点,都是由u1的标志位开始的,可以通过这个标志位来判断这个常量属于哪种常量的类型。

image-20220108211959196
     在如下例子中:

package com.openlab;

public class JVMTest01 {
    public static void main(String[] args) {
        System.out.println(1);
    }
}

     如下图所示常量池:
image-20220108212734925

     CONSTANT_POOL_COUNT 占2个字节 本例中为0x20 转换成十进制为32 说明常量池中有31个常量 ----从1开始计数 其他集合类型均从0开始。 索引值为1-31 第0项常量具有特殊意义。

五、修饰符(access_flags)

     用于表示对该类或接口的访问权限以及该类或接口的属性
image-20220108212022337

六、类的名称(this_class)

     该this_class 项目的值必须是constant_pool表中的有效索引,该constant_pool索引处的条目必须是表示此文件定义的类或接口 CONSTANT_Class_info 结构 class。

七、父类的名称(super_class)

     必须是constant_pool表中的有效索引, 如果super_class的值不为0 则constant_pool中的条目必须为CONSTANT_Class_info 结构 这个结构表示此类的文件定义的类的直接超类。直接超类不能在其classfile结构的access_flag项中设置 ACC_FINAL 标志。

​ 其实要描述的意思就是说 如果superclass指代的超类,那么它就不能被final修饰。

八、接口的数量(interfaces_count)

     接口索引计数器,占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节。

九、接口(interfaces)

     接口索引集合,一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中。

十、变量的数量(fields_count)

​      表数据,也就是测试类中只包含一个变量(不算方法内部变量)。

十一、变量(fields)

     字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量。

     在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式如下表所示:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

     与method属性值相同。

     通过举一个实例,来具体了解变量:

     第9个常量池LineNumberTable 是在后面要使用的常量的名字,在code当中被引用到。
image-20220108214243050
     第10号常量池LocalVariableTable是在后面要使用的常量的名字,在code当中被引用到。
image-20220108214308335

十二、方法的数量(methods_count)

     方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法

十三、方法(methods)

     Method属性包含三个字段值:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

     与fields属性值相同。

  • 参数列表(参数类型) 后——返回值

  • void m() 等同于 ()V

  • String toString() ——> ()Ljava/lang/String;

  • Long pos(int[] arr1,int arr2,long length) ——> ([IIJ)J

    image-20220108214615004

  • [ 一维数组

  • [[ 表示二维数组

因为在我们案例中没有声明接口和成员变量,所以对应没有展开选项的。
image-20220108214817201
1.init 无参的构造方法 —默认提供的构造方法

2.主函数main

方法中的access_flag:

image-20220108214950042

十四、附加属性(attributes和attributes_count)

     附加属性:方法中的附加属性就是code,那么code在这里是比较重要的概念,code是具体代码的实现,当我们写入方法的时候,它能够把方法中代码转化为一条条指令。
image-20220108215342688

     在官方文档中,我们可以看到很多十六进制的数字,那么它们对应方法中code的内容实现。也可以鼠标右键点击:

image-20220108215514762

image-20220108215520150

从本地变量表中第0项放入到栈空间中。

     附加属性中有的代码中存在内容,有的不存在内容;

  1. 既有预定义的属性,也可以自定义 java虚拟机会自动忽略它不认识属性

  2. Code 表示的是方法表 方法表能够编译成字节码指令,还存放了操作数栈和局部变量的信息。

    image-20220108215636746

  • u2 attribute_name_index 指向常量池中的CONSTANT_UTF8_info 存放的当前属性的名字就是code。

  • u4 attribute_length 表示的code属性的长度 (不包括前6个字节)。

  • u2 max_stack 指定当前方法被执行引擎执行的时候,在栈帧中需要分配的操作数栈的大小

  • u2 max_locals 指定当前方法被执行引擎执行的时候,在栈帧中需要分配的局部变量表的大小

  • u4 code_length 指定方法字节码的长度, class文件中每条字节码都占用一个字节

  • u1 code 存放字节码指令本身,它的长度是code_length个字节。

  • u2 exception_table_length指定异常表的大小

  • Exception_table异常表 作用对try-catch-finally的描述,可以把它看成是一个数组。每一个数组项都是一个exception_info结构, 一般来说每个catch块对应一个exception_info,编译器也可能会对当前的方法生成一些exception_info。

    image-20220108215836864

    • U2 start_pc 是从字节码code属性中的一部分,起始处到当前异常处理器的起始处的偏移量。

    • u2 end_pc 从字节码起始处到当前异常处理器 末尾的偏移量

    • u2 handler_pc 是指当前异常处理器用于处理异常(即catch块)的第一条指令相对于字节码开始处的偏移量。

    • u2 catch_type 是常量池的索引 指向的是常量池CONSTANT-Class_info 数据项,描述了catch块中的异常类型的信息。这个类必须是java.lang.Throwable的或者是它的子类。

    • 总结:
          如果偏移量从start_pc到end_pc之间,如果字节码出现了catch_type所描述的异常,那么就跳转偏移量到handler_pc的字节码中去执行。如果catch_type 为0 就代表不引用任何常量池的信息,那么这个exception_info 用于实现finally的子句。

  • u2 attribute_count 表示的是code属性中存在的其他属性的个数。会出现在 class中的属性,在field属性也有,在method属性也有。

     Attributes 可以把它看成是个数组,里面存放了code属性的其他属性:

  • ConstantValue ---- 字段表 final关键字自定义的常量值

  • Deprecated — 类 方法表 字段表

  • Exception — 异常表

  • EnclosingMethod — 类文件 局部类或匿名类的外部封装方法

  • InnerClass — 类文件 内部类列表

     可选属性:

  • LineNumberTable 源码的行号和字节码行号的对应关系,可以把这个属性看成是一个数组,

  • 数组中的每项LineNumberinfo结构描述了一条字节码和源码行号的对应关系

  • LocalVariableTable 建立了方法中的局部变量与源代码中的局部变量的对应关系。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值