背景试题
case 1
public class JavaLoading {
private static int NUM = 3;
private static JavaLoading INSTANCE = new JavaLoading();
private JavaLoading() {
NUM++;
}
public static void main(String[] args) {
System.out.println(INSTANCE.NUM);
}
}
case 2
public class JavaLoading {
private static JavaLoading INSTANCE = new JavaLoading();
private static int NUM = 3;
private JavaLoading() {
NUM++;
}
public static void main(String[] args) {
System.out.println(INSTANCE.NUM);
}
}
思考三秒钟……
case 1的输出是:4
case 2的输出是:3
根据java虚拟机规范,一个class被加载进内存后,需要进行链接(linking)与初始化(initialization)两步,
其中链接又分:验证(verification)、准备(preparation)、解析(resolution)三小步
链接 linking
验证(verification)
确保class文件符合规范,能被虚拟机正确的识别
准备(preparation)
Preparation involves creating the static fields for a class or interface and initializing such fields to their default values (§2.3, §2.4). This does not require the execution of any Java Virtual Machine code; explicit initializers for static fields are executed as part of initialization (§5.5), not preparation
准备阶段的工作包括为类和接口的静态成员变量赋默认值,这个阶段不会执行任何java虚拟机代码,静态变量的赋初始值是在初始化阶段,而不是在准备阶段(explicit initializers for static fields are executed as part of initialization , not preparation)
解析(resolution)
解析就是根据运行时常量池里的符号引用来动态决定具体值得过程。
初始化 (initialization)
初始化就是执行初始化方法,静态变量的初始值在这个阶段完成赋值。
类和接口初始化的条件
- 执行下列需要引用类或者接口的java虚拟机指令时,new/getstatic/putstatic或者invokestatic
- 在初次调用java.lang.invoke.MethodHandle实例时
- 在调用类库中的某些放射方法时,例如Class类中的java.lang.reflect包中的反射方法
- 在对类的某个子类进行初始化时
- 在它被选定为java虚拟机启动时的初始类时
分析
回过头再去分析,case 1
public class JavaLoading {
private static int NUM = 3;
private static JavaLoading INSTANCE = new JavaLoading();
private JavaLoading() {
NUM++;
}
/**
* preparation 阶段 NUM = 0,INSTANCE = null
* initialization阶段 NUM = 3,INSTANCE = new JavaLoading(),在构造函数里 NUM++了
* 所以 NUM 最终值是 4
*/
public static void main(String[] args) {
System.out.println(INSTANCE.NUM);
}
}
分析,case 2
public class JavaLoading {
private static JavaLoading INSTANCE = new JavaLoading();
private static int NUM = 3;
private JavaLoading() {
NUM++;
}
/**
* preparation 阶段 INSTANCE = null, NUM = 0
* initialization阶段 INSTANCE = new JavaLoading(),在构造函数里 NUM++了,NUM变成了1,再执行 NUM = 3,
* 所以 NUM 最终值是 3
*/
public static void main(String[] args) {
System.out.println(INSTANCE.NUM);
}
}
你学废了吗?