类加载过程
类加载过程中的“类加载”是一个整体的词,主要包括以下三个阶段:
- 加载:通过类加载器经Class文件加载进内存,提取出类的元数据存储在方法区当中,在堆当中生成Class对象。
- 连接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保类的信息符合JVM规范,没有危害安全的问题
- 准备:为静态变量分配内存,并初始化默认值(方法区),不会执行真正的赋值操作,但final生命的静态变量会在此阶段执行真正的赋值操作,不会等到初始化阶段。
- 解析:将虚拟机常量池中符号引用转为直接引用
- 初始化:执行类构造器<clinit>()方法。
- 类构造器方法是由静态代码块、静态变量赋值语句合并而成。
程序结果分析
public class Test05 {
// 如果main()方法中的语句全部注释掉,没有任何输出结果
public static void main(String[] args) {
// 主动引用:代码执行到这句话时,涉及到类A中的静态变量m,类A才开始进行初始化(执行<clinit>()方法)
// 静态变量的值在对象创建之前就已经确定
System.out.println(A.m); // 100
// 所有实例对象共享一个类变量
A a = new A(); // 执行构造函数
System.out.println(a.m); // 100
}
}
class A{
// 静态代码块和静态变量赋值语句是由初始化诱发的执行(<clinit>()方法)
static {
System.out.println("静态代码块初始化");
System.out.println(A.m); // 0
m = 300;
System.out.println(A.m); // 300
}
static int m = 100;
public A() {
System.out.println("A类的无参构造函数");
}
}
/**
* 程序运行结果:
*
* 静态代码块初始化
* 0
* 300
* 100
* A类的无参构造函数
* 100
*/
从上述代码的执行结果中可以看出代码的执行顺序:
- 准备阶段:静态变量赋默认值(m=0).
- 初始化阶段:执行类构造器方法:静态代码块和静态变量赋值操作的执行顺序与代码的先后顺序有关
- 静态代码块执行:静态代码块初始化-->0(准备阶段的初始值)-->300
- 静态变量赋值操作:m=100
- A a = new A():创建对象,执行构造函数。
什么时候会发生类的初始化
类加载过程的初始化过程:即执行<clinit>()方法的过程,即执行静态代码块和静态变量赋值操作。
下图中总结了发生类的初始化的时机,目标类被主动引用时会发生类的初始化,目标类被被动引用时不会发生类的初始化。个人理解是程序中用到了该类的静态变量(除了已经声明为final的静态变量)时,该类才会被初始化,顺便执行了静态代码块。
- 使用final修饰的静态变量,不会引发其所在类的初始化:
public class Test05 { public static void main(String[] args) { // 访问A类的final变量时,不会引发A类的初始化 System.out.println(A.n); } } /** * 程序执行结果 * 10 * */ class A{ static { System.out.println("父类被初始化"); } final static int n = 10; static int m = 100; public A() { System.out.println("父类的无参构造函数"); } } class B extends A{ static { System.out.printf("子类被初始化"); } static int b = 30; }
- 访问静态变量时,只有真正声明这个静态变量的类会被初始化(通过子类访问父类的静态变量,子类不会被初始化)
public class Test05 { public static void main(String[] args) { // 通过子类B访问父类A的静态变量,父类A被初始化,子类B没有 System.out.println(B.m); } } /** * 程序执行结果: * 父类被初始化 * 100 * */ class A{ static { System.out.println("父类被初始化"); } final static int n = 10; static int m = 100; public A() { System.out.println("父类的无参构造函数"); } } class B extends A{ static { System.out.printf("子类被初始化"); } static int b = 30; }