从今天开始分几节介绍Java中最重要的class文件。
包括class文件的结构、class是如何进入到虚拟机中的(类装载)、class的消亡(垃圾收集)
Class 的结构
Class 文件由 java 代码编译而来,每个类都会生成一个 .class 文件。 Java class 文件中包含了 java 虚拟机所需知道的、关于类或接口的所有信息。这些信息用表格的形式来说明,我们可以得到 classfile 表:如图 1
图 1 classfile 表的格式
其中类型或者为表名,或者如表 1 所示的“基本类型”。所有存储在类型 u2 、 u4 和 u8 中的值,在 class 文件中以高位在前的形式出现。
U1 |
1 个字节,无符号整形 |
U2 |
1 个字节,无符号整形 |
U3 |
1 个字节,无符号整形 |
U4 |
1 个字节,无符号整形 |
表 1 class 文件“基本类型”
Classfile 表中各项简介如下:
1) Magic( 魔数 )
每个 java class 文件的前 4 个字节被称为 magic number : 0xCAFEBABE 。魔数的作用在于,可以轻松的分辨出 java class 文件和非 java class 文件。
2) Minor_version 和 major_version
Class 文件的下面 4 个字节包含了主次版本号。随着 java 技术的发展, java class 文件格式可能会加入新特性。 Class 文件格式一旦发生变化,版本号也会随之变化。如果 class 文件的版本号超出了 java 虚拟机所能处理的有效范围, java 虚拟机将不会处理该文件。
3) Constant_pool_count 和 constant_pool
之后的是常量池。其中包含了与文件中类和接口相关的常量。常量池中存储了诸如文字字符串、 final 变量值、类名和方法名的常量。 Java 虚拟机把常量池组织为入口列表的形式。在实际列表 constant_pool 之前,是入口在列表中的计数 constant_pool_count 。
常量池中的许多入口都指向其他的常量池入口,而且 class 文件中紧随着常量池的许多条目也会指向常量池中的入口。在整个 class 文件中,指示常量池入口在常量池列表中位置的整数索引都指向这些常量池入口。列表中的一项索引值为 1 ,第二项索引值为 2 ,以此类推。
例: act.class 的常量池。
“ { }V ”表示一个返回类型为 void 的方法。
4) Access_flags
访问标志。这个字段表示的意思指明了文件中定义的是类还是接口;还定义了在类或接口的声明中,使用了哪种修饰符:是抽象的还是公共的,或者是 final 。类的类型可以为 final ,而 final 类不可能是抽象的,同时接口也不能为 final 类型。
5) This_class 和 super_class
这个类的名字和超类名字。特殊的,对于 object 类, super_class 为 0 ,除此以外 super_class 对于所有的类均有效。对于接口,在常量池入口 super_class 位置的项为 java.lang.object
。
6) Interfaces_count 和 interfaces
紧接着 super_class 的是 Interfaces_count 。此项的含义为:在文件中由该类直接实现或者由该接口所扩展的父接口的数量。在这个技术的后面,是名为 interfaces 的数组,它包含了对每个由由该类直接实现或者由该接口所扩展的父接口的常量池索引。
1) Fields_count 和 fields
紧接着 interfaces 后面的是对该类或者接口中所生命的字段的描述。首先是计数 fields_count ,它是类变量和实例变量的字段的数量总和。在这个计数后面有相应数目个 field_info 表。此表包含了字段的名字、描述符和修饰符。如果该字段被声明为 final , field_info 还会展示其常量值。
图 2 : field_info 表的格式
自上而下分别为:访问标志,简单名称(非全限定名),字段描述符,属性。字段描述符即该字段的型别。
2) Methods_count 和 methods
方法表与方法表计数。方法表的格式与 field_info 类似,区别在于其描述的是方法。故不再赘述。
3) Attributes_count 和 attributes
Java 虚拟机实现定义了两种属性 ----SourceCode 和 InnerClasses ,它们出现在 ClassFile 表中的属性列表中。
Class 的生命周期
Java 虚拟机通过装载、连接和初始化一个 java 类型,使该类型可以被正在运行的 java 程序所使用。其中,装载就是把二进制形式的 java 类型读入 java 虚拟机中;而连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中。连接阶段分为三个子步骤 ----- 验证、准备、解析。“验证”确保了 java 类型数据格式正确并且适于 java 虚拟机的使用。而“准备”步骤则负责为该类型分配它所需的内存,比如为它的类变量分配内存。“解析”步骤则负责把常量池中的符号引用转换为直接引用。
图 3 :类型生命周期的开始
装载
要装载一个类型, java 虚拟机必须:
l 通过该类型的完全限定名,产生一个代表该类型的二进制数据流
l 解析这个二进制数据流为方法去内的内部数据结构
l 创建一个俄表示该类型的 java.lang.Class 类的实例
装载步骤的最终产品就是一个 java.lang.Class 的实例对象,而这个对象就是该类型在虚拟机中的“注册”,要访问该类型的信息,程序就要调用该类型对应的 class 实例对象的方法。
所有类都由类装载器载入,载入内存中的类对应一个 java.lang.Class 实例。存在一个 Bootstrap Loader (以下简称为 BL ),由 C++ 写成,负责在虚拟机启动后一次性加载 Java 基础类库中的所有类。其他的类装载器由 java 写成,都是 java.lang.ClassLoader 的子类。除 BL 之外的所有类装载器都有一个 parent 属性,指向其父装载器。
用户自定义的类装载器是 java.lang.ClassLoader 的子类的实例,它以定制的方式装入类。
l 装载一个类时,首先要装载该类的基类及其接口
l Java 基础类由 BL 在虚拟机启动时一次性载入
l 包含 main() 的入口类由 AL 的 loadClass() 方法载入。
l 由 new 关键字创建一个类的实例。该类由运行时刻包含该 new 语句的类实例的类装载器( ClassLoader.getCallerClassLoader() )的 loadClass() 方法载入
连接
验证
连接过程的第一步是验证 - 确认类型符合 JAVA 语言的语义,并且它不会危及虚拟机的完整性。确保每个 final 类不含有子类, final 方法不能被覆盖,以及常量池中所有的域引用和方法引用有有效的名字和类型描述符号。
准备
JAVA 虚拟机为类变量分配内存,设置默认初始值(非初始化时的默认值)。
解析
初始化
在初始化阶段, Java 虚拟机设计者需要将类变量赋予正确的初始值。