H5W3
当前位置:H5W3 > 其他技术问题 > 正文

深入Java虚拟机(二)Java Class 文件

Java Class 是什么?

Java Class 文件是对Java程序二进制文件格式的精确定义。每一个Java Class文件都对一个Java类或者接口作出了全面的描述。一个Java Class文件只能包含一个类或接口。class文件不一定必须与Java语言相关,可以使用其他语言来编写程序,然后将其编译为class文件

Java class文件是8位字节的二进制流。数据项按顺序储存在class文件中,相邻项之间没有任何间隔,这样可以使得class文件紧凑。占据多个字节空间的项按照高位在前的顺序分为几个连续的字节存放。

在class文件中,可变长度项的大小和长度位于实际数据之前。这个特性使得class文件流可以从头到尾被顺序解析,首先读出项的大小,然后读出项的数据。

Class文件的内容

Java class文件中包含了Java虚拟机所需知道的、关于类或接口的所有信息。

class文件的基本类型

所有储存在类型u2、u4、u8项中的值,在class文件中以高位在前的形式出现。

类型 描述
u1 1个字节,无符号类型
u2 2个字节,无符号类型
u4 4个字节,无符号类型
u8 8个字节,无符号类型

ClassFile表的格式

可变长度的ClassFile表中的项,按照它们在class文件中出现的顺序列出了主要部分。

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count-1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

各项简介如下:

magic(魔数)

每个Java class文件的前四个字节被称为它的魔数(magic number):0xCAFEBABE。魔数的作用在于可以轻松的分辨出Java class文件。

当Java还被称为“Oak”的时候,这个魔数就已经定下来了。而选择0xCAFEBABE只不过是一个巧合。

minor_version 和 major_version

class 文件魔数后面4个字节包含了主、次版本号。随着Java技术的发展,Java class文件格式可能会假如新特性。class文件格式一旦发生变化,版本号也会随之变化。

对于Java虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,Java虚拟机才能够读取class文件。如果class文件的版本号超出了Java虚拟机所能处理的有效范围,Java虚拟机将不会处理该class文件。(遵循向下兼容的原则)

major version jdk
52 8
51 7
50 6
49 5
48 4

constant_pool_count 和 constant_pool

版本号后面的是常量池。正如上一篇所述,常量池包含了与文件中类和接口相关的常量。常量池中储存了诸如文字字符串、final变量值、类名和方法名的常量。Java虚拟机把常量池组织为入口列表的形式,在实际列表constant_pool之前的constant_pool_count,就是入口。

常量池中的许多入口都指向其他的常量池入口,而且class文件中紧随着常量池的许多条目也会指向常量池中的入口。常量池列表中的第一项索引为1,第二项索引为2,以此类推。尽管constant_pool列表中并没有索引值为0的入口,但缺失的的这一入口也被constant_pool_count计数在内。例如当constant_pool中有14项,constant_pool_count的值为15。

每个常量池入口都从一个长度为一个字节的标志开始,这个标志指出了列表中该位置的常量类型。一旦Java虚拟机获取并解析这个标志,Java虚拟机就会知道在标志后的常量类型是什么。

常量池入口标志

入口类型 标志值 描述
CONSTANT_Utf8 1 UTF-8编码的Unicode字符串
CONSTANT_Integer 3 int类型字面值
CONSTANT_Float 4 float类型字面值
CONSTANT_Long 5 lang类型字面值
CONSTANT_Double 6 double类型字面值
CONSTANT_Class 7 对一个类或接口的符号引用
CONSTANT_String 8 String 类型字面值
CONSTANT_Fieldref 9 对一个字段的符号引用
CONSTANT_Methodref 10 对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref 11 对一个接口中声明的方法的符号引用
CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用

表中的每一个标志都有一个对应的表,表名通过在标志名后加上“_info”后缀来产生。

在动态连接的Java程序中,常量池充当了十分重要的角色。除了字面常量值意外,常量池还可以容纳下面几种符号引用:

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

字段是类或接口的实例变量或者类变量。字段的描述符是一个指示字段的类型的字符串。
方法的描述符也是一个字符串,该字符串指示方法的返回值和参数的数量、顺序和类型。
在运行时,Java虚拟机使用常量池的全限定名、方法和字段的描述符,把当前类或接口中的代码与其他类或接口中的代码连接起来。由于class文件并不包含其内部组件最终内存布局的信息(不同虚拟机的实现在内存分配上的算法可能是不同的),因此类、字段和方法并不能被class文件中的字节码直接引用。Java虚拟机从常量池获取符号引用,然后在运行时解析引用项的实际地址。

access_flas

紧接着常量池后的两个字节称为access_flas。它展示了文件中定义的类或接口的几段信息。
例如,访问标志指明文件中定义的是类还是接口;访问标志还定义了在类或接口的声明中,使用了那种修饰符:抽象的还是公共的;累的类型可以为final,而final类不可能是抽象的;接口不能为final类型等。

access_flas的标志位

标志名 设置后的含义 设置者
ACC_PUBLIC 0x0001 public类型 类和接口
ACC_FINAL 0x0010 类为final类型 只有类
ACC_SUPER 0x0020 使用新型的invokespecial语义 类和接口
ACC+INTERFACE 0x0200 接口类型,不是类类型 所有接口,没有类
ACC_ABSTRACT 0x0400 abstract类型 所有接口,部分类

在access_flas中所有未使用的位都必须由编译器置0,而且Java虚拟机必须忽略它。

this_class

access_flas 后面的两个字节为this_class项,它是一个对常量池的索引。在this_class位置的常量池入口必须为CONSTANT_Class_info表。该表有两个部分组成:标签和name_index。标签部分是一个具有CONSTANT_Class值的常量;name_index位置的常量池入口是一个包含了类或接口全限定名的CONSTANT_Utf8_info表。

this_class项提供了一个如何使用常量池的范例,流程如下:

image

对于this_class来说,它只是一个指向常量池的索引。当Java虚拟机在this_class位置查询常量池入口的时候,它会发现一个把自己的标签设为CONSTANT_Class来识别自身的项。虚拟机知道CONSTANT_Class_info入口中,标签的后面总会有一个名为name_index的、指向常量池的索引,于是虚拟机根据name_index查找常量池入口,在这个位置Java虚拟机应该能找到一个容纳了类或者接口全限定名的CONSTANT_Utf8_info入口。

super_class

在class文件中,紧接在this_class之后的是super_class项,它是一个两个字节的常量池索引。

在super_class位置的常量池入口是一个指向该类超类全限定名的CONSTANT_Class_info入口。因为Java程序中所有对象的基类都是java.lang.Object类,除了Object外,常量池索引super_class对所有类均有效。对于Object类,super_class的值为0.对于接口,在常量池入口super_class位置的项为java.lang.Object

interfaces_count 和 interfaces

紧接着super_class的是interfaces_count。此项的含义为:在文件中由该类直接实现或者由该接口所扩展额父接口的数量。在这个计数的后面,是名为interfaces的数组,它包含了对每个直接实现的父接口常量池索引。

每个父接口都是用一个常量池中的CONSTANT_Class_info入口来描述,指向接口的全限定名。

fields_count 和 fields

紧接在interfaces后面的是对该类或者接口中所声明的字段的描述。首先是名为fields_count的计数,它是类变量和实例变量的字段的数量总和。在计数后面的是不同长度的field_info表的序列(fields_count指出了序列中有多少个fields_info表)

只有在class文件中由类或接口声明的字段才能在fields列表中列出,并且不会列出从超类或者父接口中继承而来的字段。

另外。fields列表可能会包含在对应的Java源文件中没有的字段,这是因为Java编译器可能会在编译时向类或接口添加字段。例如,对于一个内部类的fields列表来说,为了保持对外部类实例的引用,Java编译器会为每个外围类实例添加实例变量。而源码中并没有任何该实例的描述,它们是被Java编译器在编译时添加到fields列表中的,这些字段会通过Synthetic属性标识。

每一个field——info表都展示了一个字段的信息,包含了字段的名字、描述符和修饰符。如果该字段被声明为final,还会展示其常量值。这样的信息有些会直接放在field_info表中,有些则会放在由field_info表所指向的常量池中。

methods_count 和 methods

紧接着fields后面的是对在该类或者接口中所声明的方法的描述。首先是methods_count的计数,它是一个双字节长度的对于该类或者接口中声明的所有方法的总计数。这个总计数只包括在该类或者接口中显式定义的方法,从父类或者父接口中继承来的方法不被计入。在methods_count后面的就是方法本身,通过一个method_info表的列表进行阐述(methods_count指出了列表中有多少个method_info表)。

method_info表包含了与方法相关的一些信息,包括方法名和描述符(方法的返回值类型和参数类型)。如果方式既不是抽象的,也不是本地的,那么method_info表就包含方法局部变量所需的栈空间长度、为方法所捕获的异常表、字节码序列以及可选的行数和局部变量表。

attributes_count 和 attributes

class文件中最后的部分是属性,它给出了在该文件中类或者接口所定义的属性的基本信息。属性部分由attributes_count开始,attributes_count指出后续attributes列表中attribute_info表的数量总和。每个attribute_info的第一项是指向常量池中CONSTANT_Utf8_info表的索引,该表给出了属性的名称。

为了正确的解释Java class文件,定义了9种属性

名称 使用者 描述
Code method_info 方法的字节码和其他数据
ConstantValue field_info final变量的值
Deprecated field_info、method_info 字段或者方法被禁用的指示符
Exceptions method_info 方法可能跑出的可被检测的异常
InnerClasses ClassFile 内部、外部类的列表
LineNumberTable Code_attribute 方法的行号与字节码的映射
LocalVariableTable Code_attribute 方法的局部变量的描述
SourceFile ClassFile 源文件名
Synthetic field_info、method_info 编译器产生的字段或者方法的指示符

结语

class文件结构到这里就结束了,本文只是从深入Java虚拟机书中对class文件结构进行简单的总结和记录,要想深究class文件的结构,还需费一番功夫。

总之前进的脚步不能停,在此篇的基础上预告下一期:Java类型的生命周期

本文地址:H5W3 » 深入Java虚拟机(二)Java Class 文件

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址