类的整个生命周期包括了:加载( Loading )、验证( Verification )、准备( Preparation )、解析( Resolution )、初始化( Initialization )、使用( Using )和卸载( Unloading )七个阶段。其中验证、准备和解析三个部分统称为连接( Linking ),这七个阶段的发生顺序如下图。
上图中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序开始。但解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 语言的运行时绑定(也称为动态绑定或晚期绑定)。这里是按顺序“开始”,而不是“进行”或“完成”,是因为这些阶段通常都是互相交叉地混合式进行的,通常再一个阶段 执行的过程中调用或激活另外一个阶段。
1.1、类的初始化时机
虚拟机规范严格规定了只有四种情况(主动引用)必须对类进行“初始化”(而加载、验证、准备自然需要在此之前开始)。所有的 Java 虚拟机实现必须在每个类或接口被java程序“首次主动引用”时才初始化他们。
主动引用
- 遇到 new、getstatic、putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的常见Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的,如果其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,被标明为启动类的类(包含 main() 方法的那个类)。
被动引用
除了以上四种情况,其他使用 Java 类的方式都被看作是对类的被动使用,都不会导致类的初始化。
不属于主动使用的几种情况
1、当final 变量是基本数据类型以及 String 类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。静态变量是编译时的常量(即2.3准备阶段的中带 ConstantValue 属性的类变量),在使用这个静态变量的时候,就不会进行初始化。
import java.util.Random; class FinalTest { public static final int X = 6 / 3; public static final int Y = new Random().nextInt(100); static { System.out.println("FinalTest static block"); } } public class test { public static void main(String[] args) { System.out.println(FinalTest.X); //执行该句,没有打印静态块内输出语句 System.out.println(FinalTest.Y); //执行该句,打印了静态块内输出语句 } }