6.JVM基础-类文件结构(一)—— Class类文件结构

Java 源代码首先需要使用 Javac 编译器编译成 .class 文件,然后由 JVM 执行 .class 文件,从而程序开始运行。

JVM 只认识 .class 文件,它不关心是何种语言生成了 .class 文件,只要 .class 文件符合 JVM 的规范就能运行。目前已经有 JRuby、Jython、Scala 等语言能够在 JVM 上运行。它们有各自的语法规则,不过它们的编译器都能将各自的源码编译成符合 JVM 规范的 .class 文件,从而能够借助 JVM 运行它们。

Java 语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比 Java 语言本身更加强大。有一些 Java 语言本身无法有效支持的语言特性,不代表字节码本身无法有效支持。

 

6.1 Class 类文件的结构

Class 文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件中,中间没有添加任何分隔符,这个使得整个 Class 文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。

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

无符号数属于基本的数据类型,以u1、u2、u3、u4、u8表示1个字节、2个字节、3个字节、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
u2interfaces_class

1

u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethods_count
u2attributes_count1
attribute_infoattributesattributes_count

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 6.1.1 魔数和 Class 文件的版本

每个 class 文件的头4个字节称为魔数,它的唯一作用是确定这个文件是都为一个能被虚拟机接受的 class 文件。

紧接着魔数的4个字节是 class 文件的版本号:第5和第6个字节是次版本号,第7和第8个字节是主版本号。

 

6.1.2 常量池

紧接着主版本号之后的是常量池入口,常量池可以理解为 class 文件之中的资源仓库,它是 class 文件结构中与其他项目关联最多的数据类型,也是占用 class 文件空间最大的数据项目之一,同时还是在 class 文件中第一个出现的表类型数据项目。

常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近于 java 的常量,如文本字符串、声明 final 的常量值等。符号引用则属于编译原理的概念,包含下面三类常量:

1. 类和接口的全限定名

2. 字段的名称和描述符

3. 方法的名称和描述符

常量池中每一项常量都是一个表。结构如下:

常量常量描述项目类型类型描述
CONSTANT_Utf8_infoutf-8 编码的字符串tagu1值为1
lengthu2UTF-8 编码的字符串占用的字节数
bytesu1长度为 length 的 utf-8 编码的字符串
CONSTANT_Integer_info整型字面量tagu1值为3
bytesu4按照高位在前存储的 int 值
CONSTANT_Float_info浮点型字面量tagu1值为4
bytesu4按照高位在前存储的 float 值
CONSTANT_Long_info长整型字面量tagu1值为5
bytesu8按照高位在前存储的 long 值
CONSTANT_Double_info双精度浮点型字面量tagu1值为6
bytesu8按照高位在前存储的 double 值
CONSTANT_Class_info类或接口的符号引用tagu1值为7
indexu2指向全限定名常量项的索引
CONSTANT_String_info字符串类型的字面量tagu1值为8
indexu2指向字面量量的索引
CONSTANT_Fieldref_info字段的符号引用tagu1值为9
indexu2指向声明字段的类或者接口描述符 CONSTANT_Class_info 的索引项
indexu2指向字段描述符 CONSTANT_NameAndType 的索引项
CONSTANT_Methodref_info类中方法的符号引用tagu1值为10
indexu2指向声明方法的类描述符 CONSTANT_Class_info 的索引项
indexu2指向名称及类型描述符 CONSTANT_NameAndType 的索引项
CONSTANT_Interface-Methodref_info接口中方法的符号引用tagu1值为11
indexu2指向声明方法的接口描述符 CONSTANT_Class_info 的索引项
indexu2指向名称及类型描述符 CONSTANT_NameAndType 的索引项
CONSTANT_NameAndType_info字段或方法的部分符号引用tagu1值为12
indexu2指向该字段或方法名称常量项的索引
indexu2指向该字段或方法描述符常量项的索引
CONSTANT_Method-Handle_info表示方法句柄tagu1值为15
reference_kindu2值必须在1-9之间,它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
reference_indexu2值必须是对常量池的有效索引
CONSTANT_Method-Type_info标识方法类型tagu1值为16
descriptor_indexu2值必须是对常量池的有效索引,常量池在改索引处的项必须是 CONSTANT_Utf8_info 结构,表示方法的描述符
CONSTANT_Invoke-Dynamic_info标识一个动态方法调用点tagu1值为18
bootstrap_method_attributeu2值必须是对当前 Class 文件中引导方法表的 bootstrap_method[] 数组的有效索引
name_and_type_indexu2值必须是对当前常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,表示方法名和方法描述符

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6.1.3 访问标识

在常量池结束之后,紧接着的两个字节代表访问标识(access_flag),这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 abstract 类型;如果是类的话,是否声明为 final 等。

access_flag 中一共有16个标志位可以使用,当前只定义了其中8个,没有使用到的标志位要求一律为0。

标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义,invokespecial指令的语意在JDK 1.0.2发生过改变,为了区别这条指令使用哪种语义,JDK 1.0.2之后编译出来的类的这个标志都必须为真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

 

 

 

 

 

 

 

 

 

 

6.1.4 类索引、父类索引与接口索引集合

类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合, class 文件中由三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于java语言不允许多重继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的java类都有父类,因此除了 java.lang.Object 外,所有java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implments 语句后的接口顺序从做到右排列在接口索引集合。

类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,他们各自指向一个类型为 CONSTANT_Class_info 的类描述符常量,通过 CONSTANT_Class_info 类型的常量中的索引值可以找到定义在 CONSTANT_Utf8_info 类型的常量中的全限定名字符串。

对于接口索引集合,入口的第一项——u2类型的数据为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不在占用任何字节。

 

6.1.5 字段表集合

字段表用于描述接口或者类中声明的变量,字段包括类级变量以及实例级变量,但是不包括在方法内部声明的局部变量。

类型名称数量
u2access_flag1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

 

 

 

 

 

 

 

字段修饰符放在 access_flag 项目中,它与类中的 access_flag 项目非常类似的,都是一个u2的数据类型。

标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRANSIENT0x0080字段是否transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生的
ACC_ENUM0x4000字段是否enum

 

 

 

 

 

 

 

 

 

 

跟随 access_flags 标志的是两项索引值:name_index 和 descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。

字段表都包含的固定数据项目到 descriptor_index 为止就结束了,不过在 descriptor_index 之后跟随着一个属性集合用于存储一些额外信息,字段都可以在属性表中描述零至多项的额外信息。

标识字符含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V特殊类型void
L对象类型,如Ljava/lang/Object

 

 

 

 

 

 

 

 

 

 

 

 

6.1.6 方法表集合

Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。

类型名称数量
u2access_flag1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

 

 

 

 

 

 

 

因为 volatile 关键字和 transient 关键字不能修饰方法,所以方发表访问标志中没有了 ACC_VOLATILE 和 ACC_TRANSIENT。与之相对,增加了synchronized、native、strictfp 和 abstract 的修饰方法。

标志名称标志值含义
ACC_PUBLIC0x0001方法是否public
ACC_PRIVATE0x0002方法是否private
ACC_PROTECTED0x0004方法是否protected
ACC_STATIC0x0008方法是否static
ACC_FINAL0x0010方法是否final
ACC_SYNCHRONIZED0x0020方法是否volatilesynchronized
ACC_BRIDGE0x0040方法是否由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100方法是否native
ACC_ABSTRACT0x0400方法是否abstract
ACC_STRICTFP0x0800方法是否strictfp
ACC_SYNTHETIC0x1000方法是否由编译器自动产生的

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6.1.7 属性表集合

与 Class 文件中其他的数据项目要求严格的顺序,并且和内容不同,属性表集合的限制稍微宽松一些,不在要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,JVM运行时会忽略它不认识的属性。

虚拟机规范预定义的属性

属性名称使用位置含义
Code方发表java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量值
Deprecated类、方发表、字段表被声明为deprecated的方法和字段
Exceptions方发表方法抛出的异常
EnclosingMethod类文件仅当一个类为局部类或匿名内部类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源代码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature类、方法表、字段表JDK1.5中新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息。
SourceFile类文件记录源文件名称
SourceDebugExtension类文件JDK1.6中新增的属性,SourceDebugExtension属性用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息
Synthetic类、方法表、字段表标识方法或字段为编译器自动生成的
LocalVariableTypeTableJDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisableAnnotations类、方法表、字段表JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisableAnnotations属性用于指明哪些注解时运行时(实际上运行时就是进行反射调用)可见的。
RuntimeInvisableAnnotations类、方法表、字段表JDK1.5中新增的属性,与RuntimeVisableAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的。
RuntimeVisableParameterAnnotations方法表JDK1.5中新增的属性,作用与RuntimeVisableAnnotations类似,只不过作用对象为方法参数
RuntimeInvisableParameterAnnotations方法表JDK1.5中新增的属性,作用与RuntimeInvisableAnnotations属性类似,只不过作用对象为方法参数
AnnotationDefault方法表JDK1.5中的新增属性,用于记录注解类元素的默认值
BootstrapMethod类文件JDK1.7中新增的属性,用于保存invokedynamic指令引用的引导方法限定符

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info 类型的常量来表示,而属性的结构则完全自定义的,只需要通过一个 u4 的长度属性去说明属性值所占用的位数即可。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1info

attribute_length

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值