什么是类加载机制
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
Java的类型加载、连接和初始化过程都是在运行期间完成的
类加载的时机
一个类型从被加载到虚拟机内存中开始,到卸载为止,它的整个生命周期会经历以下七个阶段,其中验证、准备、解析三个部分统称为连接。
其中加载、验证、准备、初始化、卸载是确定的
解析阶段则不一定:它在某些情况下可以在初始化阶段之后在开始,这是为了支持Java语言的运行时绑定特性。这不是按部就班的开始,这些阶段通常都是互相交叉地混合进行,会在一个阶段执行的过程中调用、激活另一个阶段。
Java虚拟机严格规定了只有六种情况必须对类进行“初始化”:
而加载、验证、准备自然而然在前进行,其中加载在《Java虚拟机规范》中没有进行强制性约束
-
遇到
new
、getstatic
、putstatic
或invokestatic
这四条字节码时,如果类型没有进行初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:使用new关键字实例化对象
读取或者设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候
调用一个类型的静态方法的时候
使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行初始化,则需要触发其初始化。
当初始化类的时候,如果发现父类还没有进行过初始化,则需要先触发父类的初始化
当虚拟机启动时,用户需要指定一个执行主类(例如
main()
方法),虚拟机会优先初始化这个主类。使用JDK 7新的动态语言支持时,如果一个
java.lang.invoke.MethodHandle
实例最后的解 析结果为REF_getStatic
、REF_putStatic
、REF_invokeStatic
、REF_newInvokeSpecial
四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
以上六种触发类型的场景,称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用
被动引用的例子
- 通过子类引用父类的静态字段,不会导致子类初始化
package org.fenixsoft.classloading;
/**
* 被动使用类字段演示一:
* 通过子类引用父类的静态字段,不会导致子类初始化
**/
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!
因为只有父类才会被初始化,子类是否要被初始化,这需要看虚拟机的具体实现时怎样的
- 通过数组定义来引用类,不会触发此类的初始化
- 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLOWORLD = "hello world";
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);
}
}
输出结果:hello world
没有输出“ConstClass init!”,因为ConstClass类的常量HELLOWORLD,但其实在编译阶段通过常量传播优化,已经将此常量的值“hello world”直接存储在NotInitialization类的常量池中,以后NotInitialization对常量 ConstClass.HELLOWORLD的引用,实际都被转化为NotInitialization类对自身常量池的引用了。
就是说NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类编译成Class文件后就已不存在任何联系。
接口的加载过程与类加载过程稍有不同:
接口:
- 接口中不能使 用
“static{}”
语句块,而类可以 - 初始化的时候,并不要求父类接口完全初始化,只有真正用到父类接口的时候(如引用接口定义的常量),才会初始化