虚拟机把描述类数据的Class文件加载到内存,经过验证、准备、解析、初始后形成一个能被直接使用的Java类型,这个过程就是虚拟机的类加载机制。
Java语言的类加载、连接、初始化是在运行期进行的,和在编译器就完成这些步骤的其它语言相比,Java的类加载策略会使得类加载时稍微增加了一些性能开销,但换来的好处是为Java程序提供高度的灵活性,这种灵活性体现在以下两方面:
类从加载到卸载的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析阶段可合称为连接。其中加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,类加载过程就按照这种顺序开始加载,而解析阶段的顺序可能在初始化之前或之后开始。
Java虚拟机规范并没有说明何时进行类加载的第一个阶段:加载,但对于何时进行类加载过程的初始化化阶段,规范则做了严格的规定,当满足下列条件时,虚拟机必须进行类的初始化(类的加载、验证、准备阶段需要在初始化之前开始)。
接口的初始化和类的初始化稍有不同:当一个类被初始化时,要求其父类已初始化完毕;而当接口则不作此要求,即不要求父接口被初始化过,而是等到需要用到父接口时(例如引用父接口的常量)才对父接口进行初始化。
上述2.2小节的行为称之为对类进行主动引用,特点是会对类进行初始化。此外还有一种对类的被动引用,特点是引用类后并不会初始化该类。下面介绍几种被动引用的例子:
public class SuperClass { static { System.out.println("superClass init!"); } public static int value = 123; } 复制代码
public class SubClass extends SuperClass { static { System.out.print("subclass init!"); } } 复制代码
public class NotInit { public static void main(String[] args) { System.out.print(SubClass.value); } } 复制代码
上述代码运行之后,不会输出“subclass init!”,只会输出"superClass init!"。对于静态字段的访问,只有直接定义该字段的类会被初始化。因此通过子类访问定义在父类中的静态字段,只会导致父类被初始化而子类不会初被始化。
public class NotInit { public static void main(String[] args) { SuperClass[] arr = new SuperClass[10]; } } 复制代码
上述代码运行之后不会不会输出“superclass init!”,说明该类没有被初始化。
public class ConstantClass { static { System.out.println("ConstantClass init!"); } public static final int value = 10; } 复制代码
public class NotInit { public static void main(String[] args) { System.out.println(ConstantClass.value); } } 复制代码
上述代码执行后不会输出"ConstantClass init!"。当NotInit引用ConstantClass的常量时,后者的常量在编译阶段被转储到前者的常量池中,相当于前者自身已经拥有了该常量,因此不需要引用其他类的常量,也就不需要对其他类进行初始化。