一个类的完整生命周期包含:加载、验证、准备、解析、初始化、使用、卸载七个阶段。
类加载过程包含加载、验证、准备、解析、初始化五个阶段。
- 加载阶段主要做的是将编译好的类class文件加载到虚拟机内存中来
- 验证阶段主要是对类class文件进行各种校验
- 准备阶段主要做的是对类的静态变量分配内存空间并赋初始值,如果是静态常量,那么在准备阶段是直接赋予代码设置的值而不是零值
- 解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
- 初始化阶段才真正开始执行类中编写的java程序代码,初始化阶段完成的是静态变量赋值操作以及执行静态代码块,并且只初始化一次;而实例变量的赋值操作、普通代码块的执行、构造方法的执行则是在每次new实例对象的时候才会执行
当我们第一次new一个类的实例对象时,会先进行类的初始化操作,即完成静态变量赋值操作以及执行静态代码块;然后才是实例变量的赋值操作、普通代码块的执行、构造方法的执行;当我们第二次再new这个类的实例对象时,由于类已经被初始化过了,因此直接进行实例变量的赋值操作、普通代码块的执行、构造方法的执行。
以下几种情形会触发类的初始化操作:
- new对象时,如果类还未进行初始化,那么先进行初始化操作
- 调用类的静态变量或者给类的静态变量设置值,如果类还未进行初始化,那么先进行初始化操作;如果是调用类的静态常量,则不会触发类的初始化操作
- 调用类的静态方法,如果类还未进行初始化,那么先进行初始化操作
- 当初始化子类时,发现父类还未初始化,那么会先进行父类的初始化
- 虚拟机启动时,首先会初始化main方法所在的类
注意点:
- 通过子类调用父类的静态字段,只会触发父类的初始化,不会触发子类的初始化
- 通过数组定义来引用类,不会触发此类的初始化,例如A[] a = new A[10];这行代码并不会触发A类的初始化
代码举例如下:
1、无类继承关系的情形
class TestClass {
public static String a1 = "这是静态成员变量";
public String a2 = "这是实例成员变量";
public static final String a3 = "这是静态常量";
static {
System.out.println(a1);
System.out.println("这是静态代码块");
}
{
System.out.println(a2);
System.out.println("这是普通代码块");
}
public TestClass() {
System.out.println("执行构造方法");
}
}
public class MainClass {
static {
System.out.println("执行main方法所在类的静态代码块");
}
/***
* 执行main方法做了哪些操作?
* 1、TestClass.java、MainClass.java会先编译成字节码文件TestClass.class、MainClass.class
* 2、启动java虚拟机
* 3、进行类加载过程的加载、验证、准备、解析阶段(即会先将TestClass.class、MainClass.class加载到jvm中,并对TestClass类、
* MainClass类中的静态变量分配方法区的内存空间,并赋初始值;注意这里还未进行类的初始化操作,只有第一次使用类,才会触发类的初始化)
* 4、先进行main方法所在类的初始化操作
* 5、开始执行main方法
* 6、main方法中使用到哪个类,就会先触发该类的初始化操作
*/
public static void main(String[] args) {
System.out.println("开始执行main方法");
// 调用类的静态常量并不会触发类的初始化操作,因为静态常量的值已经在类加载的准备阶段赋予了用户程序代码设置的值,而并非零值
// 如果是调用类的静态变量或者静态方法,发现该类还未进行初始化,那么会先进行该类的初始化操作
System.out.println(TestClass.a3);
System.out.println("=====分界线======");
// 第一次new一个TestClass类的实例,发现该类还未进行初始化,那么会先进行类的初始化操作(静态变量赋值以及静态代码块的执行,按顺序)
// 类初始化完成之后,由于是new一个对象实例,那么会先进行实例变量的赋值以及普通代码块的执行(按顺序),再执行构造器方法
TestClass testClass1 = new TestClass();
// 第二次new一个TestClass类的实例,发现该类已经初始化过,不会再进行初始化
// 那么会先进行实例变量的赋值以及普通代码块的执行(按顺序),再执行构造器方法
TestClass testClass2 = new TestClass();
System.out.println("main方法结束,无任何非守护线程,虚拟机退出,程序运行结束");
}
}
代码执行结果:
2、有类继承关系的情形
class Super {
public static String a1 = "这是父类的静态成员变量";
public String a2 = "这是父类的实例成员变量";
static {
System.out.println(a1);
System.out.println("这是父类的静态代码块");
}
{
System.out.println(a2);
System.out.println("这是父类的普通代码块");
}
public Super() {
System.out.println("执行父类构造方法");
}
}
class Sub extends Super {
public static String b1 = "这是子类的静态成员变量";
public String b2 = "这是子类的实例成员变量";
static {
System.out.println(b1);
System.out.println("这是子类的静态代码块");
}
{
System.out.println(b2);
System.out.println("这是子类的普通代码块");
}
public Sub() {
System.out.println("执行子类构造方法");
}
}
public class ExtendClass {
static {
System.out.println("执行main方法所在类的静态代码块");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
// 第一次new一个子类的实例,发现该类还未进行初始化,并且它的父类也还未初始化,那么会先进行父类的初始化,再进行子类的初始化
// 即先按顺序执行父类的静态变量赋值和静态代码块,再按顺序执行子类的静态变量赋值和静态代码块
// 父类和子类都初始化完成之后,由于是new一个子类实例,那么会先进行父类实例变量的赋值以及父类普通代码块的执行(按顺序),再执行父类构造器方法
// 然后再进行子类实例变量的赋值以及子类普通代码块的执行(按顺序),再执行子类构造器方法
Sub sub = new Sub();
System.out.println("main方法结束,无任何非守护线程,虚拟机退出,程序运行结束");
}
}
代码执行结果:
3、关于静态常量的赋值时机
class A {
public static final String a1 = "静态常量a1";
public static final String a2;
static {
a2 = "静态常量a2";
System.out.println("执行静态代码块");
}
}
public class Test {
public static void main(String[] args) {
/***
* 调用类的静态常量是否触发类的初始化,分两种情况:
* 1、如果静态常量在声明时就赋值,那么不会触发类的初始化
* 2、如果静态常量在声明时没有赋值,而是在静态代码块赋值,那么会触发类的初始化
*/
System.out.println(A.a1);
System.out.println(A.a2);
}
}
代码执行结果: