类加载机制
一、概述
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
二、类加载时机
类从被加载到虚拟机内存中开始,到卸载出内存为止,其整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。
图2-1 类的生命周期加载、验证、准备、初始化和卸载这5个阶段顺序是确定的。解析阶段顺序不确定,支持Java语言的运行时绑定。
三、类加载过程
3.1 加载阶段
该阶段需要完成:
1) 通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
3.2 验证阶段
这一阶段确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机本身的安全。包含4个阶段的检验动作:1)文件格式验证 2)元数据验证 3)字节码验证 4)符号引用验证。
3.3 准备阶段
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。此时进行分配的变量仅包括类变量(static),不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。初始值代表数据类型的零值。
图3-1 基本数据类型零值3.4 解析阶段
该阶段将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
3.5 初始化阶段
初始化阶段是执行类构造器<clinit>()方法的过程。
四、类加载器
对于任意一个类,都需要由加载它的类加载器和类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
类加载器分为:1)启动类加载器 2)扩展类加载器 3)应用程序类加载器。应用程序都是由这3种类加载器互相配合进行加载,类加载器之间的层次关系称为类加载器的双亲委派模型。
该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。双亲委派模型工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
五、对象初始化顺序
图5-1 对象初始化顺序测试代码如下所示:
public class Parent {
private static String parent_StaticField = "父类静态变量";
private String parent_Field = "父类普通变量";
static {
System.out.println(parent_StaticField);
System.out.println("父类静态代码块");
}
{
System.out.println(parent_Field);
System.out.println("父类初始化块");
}
public Parent(){
System.out.println("父类构造器");
}
}
public class Child extends Parent{
private static String child_StaticField = "子类静态变量";
private String child_Field = "子类普通变量";
static {
System.out.println(child_StaticField);
System.out.println("子类静态代码块");
}
{
System.out.println(child_Field);
System.out.println("子类初始化块");
}
public Child(){
System.out.println("子类构造器");
}
public static void main(String[] args){
System.out.println("Main 方法");
new Child();
}
}