Java虚拟机——类文件结构(一)

《深入理解Java虚拟机》第二版 周志明

第六章 类文件结构 p162

无关性基石

时至今日,商业机构和开源机构已经在Java语言之外发展出一大批在Java虚拟机之上运行的语言,如Clojure、Groovy、 JRuby、 Jython、 Scala 等。

实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java 虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class 文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。

任何其他语言的实现者都可以将Java虚拟机作为语言的产品交付媒介。例如,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其他语言的编译器一样可以把程序代码编译成Class文件,虚拟机并不关心Class的来源是何种语言,如图6-1所示。

Java语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比Java语言本身更加强大。因此,有一些Java语言本身无法有效支持的语言特性不代表字节码本身无法有效支持,这也为其他语言实现一些有别于Java的语言特性提供了基础。
Java虚拟机——类文件结构(一)

Class类文件的结构

Java虚拟机——类文件结构(一)

1. 魔数与Class文件的版本

0xCAFEBABE

2. 常量池

常量池中主要存放两大类常量+字面量(LiteraD和符号引用:(Symbolic References).字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的全限定名(Fally Qualifed Name )
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符
    (在JDK的bin目录中,Oracle公司已经为我们准备好一个专门用于分析Class文件字节码的工具:javap;)

3. 访问标志

Java虚拟机——类文件结构(一)

4. 类索引、父类索引与接口索引集合

类索引(this_ class) 和父类索引(super clas)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。接口索集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句后的接口顺序从左到右排列在接口索引集合中。

5. 字段表集合 p175

Java虚拟机——类文件结构(一)Java虚拟机——类文件结构(一)Java虚拟机——类文件结构(一)

6. 方法表集合 p178

方法的定义可以通过访问标志、名称索引、描述符索引表达清楚,但方法里面的代码去哪里了?方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面。
Java虚拟机——类文件结构(一)
Java虚拟机——类文件结构(一)

7. 属性表集合 p180

· code属性 p182

Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。
Java虚拟机——类文件结构(一)

· Exceptions属性 p188

Java虚拟机——类文件结构(一)

· LineNumberTable属性 p189

LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,如果选择不生成LineNumberTable属性,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。
Java虚拟机——类文件结构(一)
line_ number_table 是- -个数量为linc_ number_tablc_ length、 类型为line_ number_ info 的集合,line_ number_ jinfo 表包括了start_ pc和line_ number 两个u2类型的数据项,前者是字节码行号,后者是Java源码行号。

· LocalVariableTable属性 p189

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有的参数名称都将会丢失,IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名,这对程序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。

· SourceFile属性 p190

SourceFile属性用于记录生成这个Class文件的源码文件名称。这个属性也是可选的,如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错代码所属的文件名。

· ConstantValue属性 p191

ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)可以使用这项属性。

· InnerClasses属性 p192

InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。

· Deprecated及Synthetic属性 p193

Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@deprecated注释进行设置。
Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的,其中最典型的例子就是Bridge Method。

· StackMapTable属性 p193

StackMapTable属性在JDK 1.6 发布后增加到了Class 文件规范中,它是一-个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用(见7.3.2节),目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
StackMapTable属性中包含零至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地代表了一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。类型检查验证器会通过检查自标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。StackMapTable 属性的结构见表6-27。
Java虚拟机——类文件结构(一)

· Signature属性 p194

Signature属性在JDK 15发布后增加到了Class文件规范之中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在JIDK 1.s中大幅增强了Jjava语言的语法,在此之后,任何类:按口、初始化方法或成员的泛型签名如果包含了类型变量(cTypeVariabies)"或参数化类型(Parametenized Types ),则Signature属性会为它记录泛型签名信息。

· BootstrapMethods属性 p195

这个属性用于保存invokedynamic指令引用的引导方法限定符。