目录
1. 概述
Java语言中,类的加载、链接和初始化都是在程序运行期间完成的。
加载、验证、准备、初始化和卸载5个步骤是按部就班地开始的。解析可以在初始化之后。
2. 类加载的时机
必须对类进行“初始化”的情况:(对一个类进行主动引用)
- 遇到new、getstatic、putstatic、invokestatic这四条指令。即实例化对象、读取或设置类的静态字段、调用静态方法。
- Java.lang.reflect包对类进行反射调用的时候。
- 初始化一个类时,如果其父类还没有初始化,要先触发父类的初始化。
- 虚拟机启动时,包含main()方法的类要初始化
- 反射类
不会触发初始化的引用类的方式称为被动引用。
3. 类加载的过程
加载、验证、准备、解析、初始化
3.1 加载
- 获取类的二进制字节流
- 将静态存储结构转化为方法区的运行时数据结构
- 在内存中生成这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
数组类的加载过程:
- 如果数组的组件是引用类型,递归采用本节中定义的加载过程加载
- 如果不是引用类型,将数组标记为与引导类加载器关联
- 如果组件类型不是引用类型, 数组类的可见性默认为public
加载与连接(验证、准备、解析)阶段是交叉进行的,但是开始时间是固定的。
3.2 验证
验证是对虚拟机自身保护的一项重要工作。四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。
1. 文件格式验证
字节流是否符合class文件格式规范。保证输入的字节流能正确地解析并存储在方法区之内。
后面的验证都是基于方法区的存储结构进行,不在操作字节流
2. 元数据验证
对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范。目的是对类的元数据信息进行语义校验。保证不存在不符合java语义规范的元数据信息
3. 字节码验证
最复杂的校验,通过数据流和控制流分析,确定程序语义是合法的。
4. 符号引用验证
最后一个阶段是在虚拟机将符号引用转化为直接引用的时候。目的是确保解析动作能正常执行。发生在解析阶段。
3.3 准备
为类变量分配内存并设置类变量初始值的阶段,在方法区进行分配。只有类变量、而不包括实例变量。初始化阶段值都是0或false或null。
3.4 解析
常量池内的符号引用替换为直接引用。
符号引用:一组符号,可以是任意字面量,只要能无歧义地定位目标即可。引用目标不一定已经加载到内存中。
直接引用:直接指向目标的指针、相对偏移量、间接定位到目标的句柄。有了直接引用、引用的目标必定已经在内存中了。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。
(1)类或接口的解析
类D中的符号应用N解析为类或接口C
如果C不是数组类型,JVM将N代表的全限定名传递给D的类加载器去加载类C。中间可能触发其他类加载动作。
如果C是数组类型,且数组元素类型为对象。JVM先加载数组元素类型,然后生成一个代表这个数组维度和元素的数组对象。
如果前两种情况都没有异常,C在JVM中就是一个有效的类或接口了,但在解析完成前要进行符号引用验证,确认D是否具备对C的访问权限。
(2)字段解析
首先对字段所属的类或接口的符号引用进行解析。所属类或接口用C表示。
如果C本省包含该字段,返回字段的直接应用,结束。
如果C中实现了接口,按照继承关系从下往上递归搜索各个接口。如果包含该字段,返回字段的直接引用,查找结束。
如果C不是java.lang.Object,按照继承关系从下往上找其父类,如果在父类中找到该字段,返回直接运用。
否则查找失败,抛出java.lang.NoSuchFieldError异常。
(3)类方法解析
首先对字段所属的类或接口的符号引用进行解析。所属类或接口用C表示。(与字段解析相同)
如果C是个接口,直接抛出异常
C是类,查找是否有简单名称和描述符符合目标,有则返回这个方法的引用。
否则,在类C的父类中查找,有则返回方法的直接引用。
否则,在类C实现的接口列表及他们的父接口中递归查找是否有,如果有说明类C是抽象类,查找结束并抛出一场
否则,查找失败
(4)接口方法解析
首先对字段所属的类或接口的符号引用进行解析。所属类或接口用C表示。(与字段解析相同)
如果C是个类,直接抛出异常
在接口C中查找,有则返回直接引用
否则,在接口C的父接口中查找,有则返回方法的直接引用
否则,查找失败
3.5 初始化
是类加载过程的最后一步,执行类构造器<clinit>()方法
<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中语句合并产生的。注意静态变量的访问顺序,要先定义再访问。
<clinit>()与类的构造函数不同,不需要显示调用父类构造器。
4. 类加载器
“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作在JVM外部实现,称为“类加载器”
通过同一个类加载器加载的类,instanceof 为true