深入理解Java虚拟机 第2版 周志明著(五)

第6章 类文件结构

6.1
Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前(大端存储)的方式分割成若干个8位字节进行存储。

Class文件格式结构中只有两种数据类型:无符号数和表。

  • 无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数组,索引引用,数量值或者按照UTF-8编码构成字符串值。
  • 表:是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以_info结尾。表用于描述有层次关系的复合结构的数据。

整个Class文件本质上就是一张表,它由下图所示的数据项构成。
在这里插入图片描述
6.1.1 魔数与Class文件的版本
每个Class文件的头4个字节称为魔数,作用是用作身份识别。Class文件的魔数值为:0xCAFEBABE(咖啡宝贝),紧接着的u2,u2,4个字节存储的是Class文件的版本号,第一个u2是次版本号,第二个是主版本号。Java的版本号是从45开始的,JDK1.1以后的每个JDK大版本发布主版本号向上+1。

6.1.2 常量池
由于常量池中常量的数量是不固定的,所以在常量池的入口放置一项u2类型的数据,代表常量池容量计数值,这个容量计数值是从1而不是从0开始。将第0项空出来是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达不引用任何一个常量池项目的含义,这种情况可以把索引值置为0来表示,Class文件结构中只有常量池的容量计数是从1开始的。
常量池中主要存放两大类常量:字面量和符号引用。

  • 字面量:比较接近于Java语言层面的常量概念,如文本字符串,声明为final的常量值等。
  • 符号引用:1.类和接口的全限定名。2.字段的名称和描述符。3.方法的名称和描述符。

常量池中的每一项都是一个表,截止到JDK1.7,一共有14种结构各不相同的表结构。这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型。14种常量类型各自均有自己的结构。
在这里插入图片描述
6.1.3 访问标志
在常量池结束后,是俩个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息。
在这里插入图片描述

6.1.4 类索引,父类索引与接口索引集合
类索引:u2类型,确定这个类的全限定名。
父类索引:u2类型,用于确定这个类的父类的全限定名,除了java.lang.Object之外,所有的Java类都有父类,即除了java.lang.Object之外所有Java类的父类索引都不为0.
接口索引集合:一组u2类型的数据的集合。用来描述这个类实现了哪些接口。
对于接口索引集合,入口的第一项——u2类型的数据为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。

6.1.5 字段表集合
字段表用于描述接口或类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
在这里插入图片描述
字段修饰符放在access_flags项目中。
在这里插入图片描述
跟随access_flags标志的是两项索引值:name_index和descriptor_index。他们都是对常量池的引用。分别代表着字段的简单名称已经字段和方法的描述符。

  • 简单名称:是指没有类型和参数修饰的方法或者字段名称,例如一个方法test()和private String str字段的简单名称是inc和str。
  • 描述符:作用是用来描述字段的数据类型,方法的参数列表(包括数量,类型已经顺序)和返回值。
    在这里插入图片描述
    对于数组类型,每一维度用一个前置的“[”表示,例如:int[] 为 [I,java.lang.String[]为 [[Ljava/lang/String。
    用描述符描述方法时,按照先参数列表后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号()之内。例如:void test()的描述符为()V。java.lang.String toString()的描述符为()Ljava/lang/String。

之后跟着的是属性表集合用于存储一些额外的信息,例如:private int i = 2;可能就会存在一项名称为ConstantValue的属性,值指向常量123。关于属性表的具体介绍,慢慢往下看。
字段表集合中不会列出从超类或者父类接口中继承而来的字段。

6.1.6 方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。仅在访问标志和属性表集合的可选项中有所区别。
在这里插入图片描述
在这里插入图片描述
方法里面的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中的一个名为Code的属性里面。
如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。

6.1.7 属性表集合
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。一个符合规则的属性表应该满足下图中所定义的表结构。
在这里插入图片描述
Java虚拟机运行时会忽略掉它不认识的属性。预定义属性已经增加到21项。如下图。
在这里插入图片描述

  1. Code属性
    Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。
    在这里插入图片描述
    如果当字节码在第start_pc行到第end_pc行(不包含end_pc行,此处的行是一种形象的描述,指的是字节码相对于方法体开始偏移量,而不是Java源码的行号)之间出现了类型为catch_type或者其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向到handler_pc处进行处理。
    在这里插入图片描述

  2. Exceptions属性
    列取出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
    在这里插入图片描述
    number_of_exceptions表示可能抛出number_of_exceptions种受查异常,每一种受查异常使用一个exception_index_table项表示,exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型。

  3. LineNumberTable属性
    用于描述Java源码行号与字节码行号之间的对应关系,并不是运行时必须的属性,但是会默认生成到Class文件之中。
    在这里插入图片描述
    line_number_table是一个数量为line_number_table_length,类型为line_number_info的集合,line_number_info表包括了start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号。

  4. LocalVariableTable属性
    用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。
    在这里插入图片描述
    local_variable_info项目代表了一个栈帧与源码中的局部变量的关联。
    在这里插入图片描述
    LocalVariableTable属性增加了一个“姐妹属性”:LocalVariableTypeTable,这个新增的属性结构与LocalVariableTable非常类似,仅仅是把记录的字段描述符的descriptor_index替换成了字段的特征签名,对于非泛型类型来说,描述符和特征签名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉,描述符就不能准确地描述泛型类型了,因此出现了LocalVariableTypeTable。

  5. SourceFile
    用于记录生成这个Class文件的源码文件的名称。
    在这里插入图片描述

  6. ConstantValue属性
    作用是通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量才可以使用这项属性。如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在方法中进行初始化。
    在这里插入图片描述

  7. InnerClasses属性
    用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。
    在这里插入图片描述
    number_of_classes代表需要记录多少个内部类信息。每一个内部类信息由一个inner_classes_info表进行描述。inner_classes_info表结构如下。
    在这里插入图片描述
    inner_class_info_index和outer_class_info_index分别代表了内部类和宿主类的符号引用。inner_name_index代表这个内部类的名称,如果是匿名内部类,那么这项值为0。inner_class_access_flags是内部类的访问标志。
    在这里插入图片描述

  8. Deprecated及Synthetic属性
    Deprecated和Synthetic都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概。
    Deprecated属性用于表示某个类,字段或者,方法,已经被程序作者定为不再推荐使用,可以通过@deprecated注解设置。
    Synthetic属性代表此字段或者方法不是有java源码直接产生的,是由编译器自行添加的。
    在这里插入图片描述

  9. StackMapTable属性
    JDK1.6后增加到了Class文件规范中,它是一个复杂的变成属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
    StackMapTable属性中包含零至多个栈映射帧,每个栈映射帧都显示或隐式地代表了一个字节码偏移量,用于表示该执行到该字节码时局部表里表和操作数栈的验证类型。类型检查验证器会通过检查目标方法的局部标量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
    在这里插入图片描述
    在版本号大于或等于 50.0 的 Class 文件中,如果方法的 Code 属性中没有附带StackMapTable属性,那就意味着它带有一个隐式的StackMap属性。这个
    StackMap属性的作用等同于number_of_entries值为0的StackMapTable属性。一个方法的Code属性最多只能有一个StackMapTable属性,否则将抛出ClassFormatError异常。

  10. Signature属性
    JDK1.5之后添加的。是一个可选的定长属性,可以出现于类,字段表和方法表结构的属性表中。任何类,接口,初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。
    在这里插入图片描述

  11. BootstrapMethods属性
    位于类文件的属性表中,用于保存invokedynamic指令引用的引导方法限定符。如果某个类文件结构的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,类文件的属性表中最多也只能有一个BootstrapMethods属性。BootstrapMethods属性与JSP-292中的InvokeDynamic指令和java.lang.Invoke包关系非常密切。
    在这里插入图片描述
    在这里插入图片描述
    bootstrap_method_ref:bootstrap_method_ref项的值必须是一个对常量池的有效索引。常量池在该索引处的值必须是一个CONSTANT_MethodHandle_info结构。
    num_bootstrap_arguments:num_bootstrap_arguments项的值给出了bootstrap_methods[]数组成员的数量。
    bootstrap_arguments[]:bootstrap_arguments[]数组的每个成员必须是一个队常量池有效索引。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值