六、JVM(HotSpot)类文件结构

注:本博文主要是基于JDK1.7会适当加入1.8内容。

实现语言无关性的基础是虚拟机和字节码的存储形式。
Java程序(.java)——>javac编译器——–>字节码(.class)—>Java虚拟机(HotSpot)
JRuby程序(.rb)——->jrubyc编译器——>字节码(.class)—>Java虚拟机(HotSpot)
Groovy程序(.java)—>groovyc编译器—>字节码(.class)—>Java虚拟机(HotSpot)
其他程序—————–>对应编译器——–>字节码(.class)—>Java虚拟机(HotSpot)

1、Class类文件的结构

据Java虚拟机规范规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪数据只有两种数据结构:无符号数和表

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

(1)魔数与Class文件版本
每个Class文件的头4个字节称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件,相当于token理解,值0xCAFEBABE。代表次版本号为第5和第6个字节,主版本号为第7和第8个字节。
(2)常量池
紧接着次版本号和主版本号的是常量池入口。由于常量池中常量的数量不确定,所以常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值。需要注意的是,常量池容量统计从1开始,而非从0开始,若u2的值为0x0016十进制则为22,表示1-21个常量,0项常量空出来为特殊考虑,这样做的目的在于满足后面某些指向常量池的索引值数据在特定情况下需要表达“不引用任何一个常量池的项目”。
常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串,声明为final的常量值,符号引用则属于编译原理方面的概念,包括三类常量:类和接口全限定名;字段的名称和描述符;方法的名称和描述符。
(3)访问标志
用于识别一些类或者接口层次的信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话是否声明为final类型等。access_flags一共有16个标志位可用,当前使用了8个,没有使用的标志位一律为0。
(4)类索引、父类索引和接口索引集合
类索引和父类索引都是一个u2类型数据,而接口索引集合是一组u2的数据集合,Class文件中由这三项数据来确定这个类的继承实现关系。类索引确定这个类的全限定名;父类索引确定这个类的父类全限定名,除了java.lang.Object所有的类都应该有父类,java.lang.Object的父类索引为0;接口索引集合描述这个类实现哪些接口,这些被实现的接口将按照implements语句后的顺序从左到右排列在接口索引集合中。
(5)字段表集合
描述类或者接口中声明的变量,字段的范围包括类级变量和实例级变量,但不包括方法内声明的局部变量。包括的信息:字段作用域(public、protected、private),实例变量或类变量(static),可变性(final),并发可见性(volatile),是否被序列化(transient),字段数据类型(基本类型、对象、数组),字段名称。
(6)方法表集合
方法表和字段表访问标志区别:字段表中的volatile对应ACC_VOLATILE、transient对应ACC_TRANSIENT;方法表中有synchronized对应ACC_SYNCHRONIZED、native对应ACC_NATIVE、strictfp对应ACC_STRICTFP、abstract对话ingACC_ABSTRACT。方法体内的Java代码,经过编译器编译成字节码指令后,存放在方法属性表中一个名为“Code”的属性中,属性表作为Class文件格式中最具扩展性的一种数据项目。
(7)属性表集合
Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有信息。

  • Code属性。Java程序方法体中代码经过javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合中,但并非所有的方法表中都必须存在这个属性,譬如接口或者抽象类的方法就不存在Code属性(抽象类中也有方法存在Code属性,非抽象方法)。Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法体里面的Java代码)和元数据(Metadata,包括类、字段、方法定义以及其他信息)两部分,那么整个Class文件中,Code属性用于描述代码,所有其他的数据项目都用于描述元数据。
  • Exceptions属性。列举出方法可能抛出的受检查异常,也就是方法描述时throws关键字后面列举的异常。
  • LineNumberTable属性。描述Java源码行号和字节码行号(字节码偏移量)之间的关系,-g:none和-g:lines取消或者生成这项信息。
  • LocalVariableTable属性。描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,-g:none和-g:vars取消或者生成这项信息。JDK1.5引入泛型后,LocalVariableTable属性增加相似性属性为LocalVariableTypeTable,仅仅是将字段描述符的description_index替换成字段的特征签名(Signature),对于非泛型类型来说,描述符合特征签名能描述的信息是基本一致的,对于泛型,由于描述符中泛型的参数化类型被擦除,描述符不能准确描述泛型类型,因此出现LocalVariableTypeTable。
  • sourceFile属性。记录生成这个Class文件的源码文件名称,可选,-g:none和-g:source取消或者生成这项信息。大多数情况下,类名和文件名是一致的(Java语法要求),但是有一些特殊情况(如内部类)类名和文件名是不一致的。
  • ConstantValue属性。通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才能使用这项属性。对应非static类型变量(实例变量)的赋值是在实例构造器< init >方法中进行的,对于static类型变量(类变量)有两种方式选择,类构造器< clinit >方法或者使用ConstantValue属性。目前javac编译器选择:如果同时使用final和static修饰的变量,且同时数据类型为基本数据类型或者String的话,使用ConstantValue属性赋值;如果没有被final修饰,或者数据类型非基本数据类型或String类型,则在< clinit >方法中初始化。
  • InnerClasses属性。记录内部类和宿主类之间的关系,如果类中定义了内部类,编译器将会为它以及它所包含的内部类生成InnerClass属性。
  • Deprecated及Synthetic属性。属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。Deprecated用于表示某个类、字段或者方法,已经被作者定为不再推荐使用,代码中用@deprecated注解进行设置。Synthetic代表此字段或者方法并不是由Java源码生成,而是由编译器自行添加的,在JDK1.5之后,表示一个类、字段、方法是编译器自动产生的,也可以设置它们的访问标志中ACC_SYNTHETIC标志位,其中最典型的例子是Bridge_Method,唯一例外的是实例构造器< init >方法和类构造器< clinit >方法。
  • StackMapTable属性。JDK1.6引入,它是复杂的变长属性,位于Code属性的属性表中,会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替比较消耗性能的基于数据流分析类型推导验证器。
  • Signature属性。JDK1.5引入,它是可选的定长属性,出现在类、属性表和方法表结构的属性表中。JDK1.5之后,任何类、接口、初始化方法或者成员的泛型签名如果包含了类型变量(TypeVariable)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息,之所以设置这样一个属性记录泛型类型,因为Java语言中泛型采用的是擦除法实现的伪泛型,在字节码(Code属性)中,反省信息编译之后统统被擦除掉。运行时反射时就无法获取的到泛型信息,Signature属性就是为了弥补这个缺陷新增的。
  • BootstrapMethods属性。JDK1.7引入,它是复杂变长属性,用于保存invokedynamic指令引用的引导方法限定符。如果某个类文件结构的常量池中曾经出现CONSTANT_InvokeDynamic_info类型常量则必须属性表中存在一个BootstrapMethods属性,如果出现多个也只存储一个BootstrapMethods属性。

    2、字节码指令

    Java虚拟机指令由一个字节长度、代表着谋者特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需参数(操作数)构成。

  • 字节码与数据类型

  • 加载与存储指令
  • 运算指令
  • 类型转换指令
  • 对象创建与访问指令
  • 操作数栈管理指令
  • 控制转移指令
  • 方法调用和返回指令
  • 异常初灵指令
  • 同步之灵

3、公有设计与私有设计

私有设计时,选择哪种特性取决于Java虚拟机实现的目标和关注点是什么?两种方式考量:
(1)将输入Java虚拟机代码在加载或执行时翻译成另外一种虚拟机的指令集;
(2)将输入Java虚拟机代码在加载或执行时翻译成宿主CPU的本地指令集(JIT代码生成技术)。

猜你喜欢

转载自blog.csdn.net/zhangwei408089826/article/details/81666326