Class加载过程

Class文件的加载流程如下图所示,其中加载、验证、准备、初始化、和卸载的顺序是固定的,解析有可能会在初始化之后进行

类装载的条件
Class只有被使用到的时候才会被装载,一个类或接口在第一次主动使用时,必须要进行初始化。主动使用包括以下几种情况:

当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化;

当调用类的静态方法时,也就是使用了invokestatic指令;

当使用类或接口的静态字段(非final的常量),比如使用getstatic或putstatic指令;

当使用java.lang.reflect包中的方法,反射类的方法时;

当初始化子类时,首先要先初始化父类;

主函数的入口(main()函数);

下面看一个被动引用的例子:


/**
 * 被动使用类字段演示:通过子类引用父类的静态字段,不会导致子类初始化
 * 
 * @author xuefeihu
 *
 */
public class SuperClass {

	static {
		System.out.println("SuperClass init!");
	}
	
	public static int value = 123;
}

public class SubClass extends SuperClass {
	
	static {
		System.out.println("SubClass init!");
	}

}

public class NotInitialization {

	public static void main(String[] args) {
		System.out.println(SubClass.value);
	}
}

运行后的结果为:

SuperClass init!
123

下面看一个被动引用的例子:

// 加载Child之前会去先加载Parent
public class Parent {
	static {
		System.out.println("Parent init");
	}
}

public class Child extends Parent {
	static {
		System.out.println("Child init");
	}
}

public class InitMain {
	public static void main(String[] args) {
		Child c = new Child();
	}
}

执行结果如下:

Parent init
Child init

加载

在类加载阶段主要完成以下三件事情:①通过此类的全限定名来获取此类的二进制字节流。②将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。③在java堆中生成对应的java.lang.Class对象。java字节码的获取方式可以有以下几种方式:

(1)从ZIP包中获取(如jar、ear、war等)

(2)从网络中获取

(3)运行时动态生成,使用最多的就是动态代理(如:JDK动态代理、CGLIB动态代理)

(4)其他文件生成(如JSP编译生成对应的class文件)

(5)从数据库中读取(相对较少)

验证

验证的目的是保证字节码文件是正确的。验证的方式主要有以下几种:①文件格式的验证;②元数据验证;③字节码验证(很复杂);④符号引用验证

(1)文件格式的验证

①是否以0XCAFEBABY开头

②主次版本号是否在当前JVM处理范围之内

③常量池中是否有不被支持的常量

......

(2)元数据验证

①是否有父类

②继承了final类?

③非抽象类实现了所有的抽象方法

......

(3)字节码验证

①运行检查

②栈数据类型和操作码数据参数吻合

③跳转指令指定到合理的位置

......

(4)符号引用验证

①符号引用中通过字符串描述的权限定名是否能找到对应的类

②类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段

③符号引用中的类、字段、方法的访问性是否可以被当前类访问

......

准备

准备阶段就是为static类型的变量分配初始值(如int类型的初始值为0),需要注意的是当变量类型为static final时,准备阶段就将其赋值为最终的值。

解析

JVM将常量池内的符号引用(与JVM无关)转化为直接引用(与JVM相关)。解析动作主要针对于类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用。

初始化

初始化阶段包括执行构造器,其中包括了static变量赋值语句和static{}静态块;子类的调用之前保证父类的被调用;还有一点就是是线程安全的。


参考:《深入理解Java虚拟机》