类初始化
初始化时机:
初始化是执行 class 或 interface 的初始化方法 <init>
, 以下内容来自 JVM 文档 1 :
- 当执行这些虚拟机指令时: new T, 存取 T 的静态变量/方法
- new, 静态资源相关的 MethodHandle 实例被调用
- 一些反射方法被调用. 例如 Class 的方法
- T(T 是 class) 的子类被初始化
- T(T 是 interface) 的实现类被初始化
- T 是JVM启动时的初始化类
以下内容来自 Java 文档 2 :
- T is a class and an instance of T is created.
- A static method declared by T is invoked.
- A static field declared by T is assigned.
- A static field declared by T is used and the field is not a constant variable (§4.12.4).
- T is a top level class (§7.6) and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.
循环依赖:
package com.lvw.jvm.init_class;
public class CircleInitializing {
public static void main(String[] args) {
int f = Sub.f;
}
static class Super {
static int f = 1; // 1
static { // 2
System.out.println("Super is initializing.");
Sub.run(); // 3
System.out.println("Super was initialized.");
}
static void run() {
System.out.println("Super.run() was called.");
}
}
static class Sub extends Super { // 4 recursive init for Super
static {
System.out.println("Sub is initializing.");
Super.run();
System.out.println("Sub was initialized.");
}
static void run() {
System.out.println("Sub.run() was called.");
}
}
}
/*
output:
Super is initializing.
Sub is initializing.
Super.run() was called.
Sub was initialized.
Sub.run() was called.
Super was initialized.
*/
循环依赖的解决方法:
首先, 在 C 的 Class 对象中保存一个状态, 这个状态有四个取值:
- C 已经验证ok且准备好了, 但未被初始化
- C 正在被初始化
- C 已经初始化完成
- C 初始化失败
其次, 维护一个 Lock, 用来串行化初始化.
对于一个初始化线程, 具体初始步骤为:
- 获取 Lock
- 如果 C 正在被其他线程初始化, 则 block 自己
- #todo
关于循环依赖的原文看这里
碎片补充:
- 涉及某个类不一定会初始化, 只有静态资源被访问才会导致静态资源所在的类被初始化. 举一个例子: 如果 A 继承 B { static int f = 1;}, 对
A.f
的访问会导致 B 的初始化, 但不会初始化 A. - 初始化中的循环依赖的解决方法: 在 Class 对象中
- static final a = 1; 是编译期常量, 对这种常量的访问不会导致初始化.
- static final a = random(); 这是运行期常量, 这会导致初始化.
实例初始化
步骤:
- 递归初始化父类
- 初始化
- 初始化 Class
- 类变量赋值(static) 只执行一次
- 静态初始化块(static {}) 只执行一次
- 初始化实例
- 实例变量赋值(非static) 每个对象执行一次
- 实例变量初始化块({}) 每个对象执行一次
- 构造函数 每个对象执行一次
- 初始化 Class
不要在父类的构造方法中调用子类的方法.
实例初始化的练习
设 C 为当前正在实例化的类型. C初始值 = T
1 找到 C 的 Class object
2.a 如果有父类或接口, 先初始化父类和接口 (回到0)
2.b 如果没有父类和接口, 继续 3
3.a 如果还未初始化, 则先初始化
从上到下, 初始化类变量, 执行静态初始化块, 如果遇到 new 或者 静态资源引用了其他类 D, C = D, 回到 1. 否则继续 4.
3.b 如果正在初始化或已经完成初始化, 则继续 4
通过 Class 的状态可以判断出是否正在初始化, 如果是在初始化中, 说明发生了递归, 此时无需再次执行初始化逻辑.
4 为实例变量赋值
遇到 new 回到 0.
5 执行构造函数
如果是在初始化中, 静态变量可能是初始值或者null.
练习1
package com.lvw.jvm.init_class;
class CA {
private static String ivar = null;
static CB other = new CB("CA.other of static");
static {
System.out.println("in CA init");
ivar = new String("2");
}
CA(String caller) {
System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
}
}
class CB {
public int ivar = 2;
static {
System.out.println("in CB init");
}
static CA other1 = new CA("CB.other1");
CA other2 = new CA("CB.other2");
CB(String caller) {
System.out.println("CB.CONSTRUCTOR by " + caller);
}
public static void main(String[] args) {
CB cb = new CB("main");
}
}
开始分析, 先写出第一层
CB cb = new CB("main");
static{ System.out.println("in CB init"); } // 静态初始化块 步骤 3.a
static CA other1 = new CA("CB.other1"); // 类变量 步骤 3.a
CA other2 = new CA("CB.other2"); // 实例变量 步骤 4
CB <CONSTRUCTOR> System.out.println("CB.CONSTRUCTOR by " + caller); // 构造方法 步骤 5
然后补充第二层, 第三层…, 每缩进一层相当于回到步骤1
CB cb = new CB("main");
static{ System.out.println("in CB init"); } // 静态初始化块 3.a
static CA other1 = new CA("CB.other1"); // 类变量 3.a
static String ivar = null; // 类变量 3.a
static CB other = new CB("CA.other of static"); // 初始化 3.a
CA other2 = new CA("CB.other2"); // 实例变量 3.b
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CB <CONSTRUCTOR> System.out.println("CB.CONSTRUCTOR by " + caller);
static {System.out.println("in CA init"); ivar = new String("2");} // 静态初始化块 3.a
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar); // 构造方法 5
CA other2 = new CA("CB.other2"); // 实例变量 4
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CB <CONSTRUCTOR> System.out.println("CB.CONSTRUCTOR by " + caller); // 构造方法 5
分析结果: 即把所有 System.out.println
按顺序写出来:
in CB init
CA.CONSTRUCTOR by CB.other2, ivar: null
CB.CONSTRUCTOR by CA.other of static
in CA init
CA.CONSTRUCTOR by CB.other1, ivar: 2
CA.CONSTRUCTOR by CB.other2, ivar: 2
CB.CONSTRUCTOR by main
注意上面的 ivar
变量第一次被引用的时候, 还未被初始化.
练习2
package com.lvw.jvm.init_class;
class CA {
private static String ivar = null;
static CB other = new CB("CA.other of static");
static {
System.out.println("in CA init");
ivar = new String("2");
}
CA(String caller) {
System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
}
}
class CB {
public int ivar = 2;
static {
System.out.println("in CB init");
}
CA other1 = new CA("CB.other1"); // 去掉了 static
CA other2 = new CA("CB.other2");
CB(String caller) {
System.out.println("CB.CONSTRUCTOR by " + caller);
}
public static void main(String[] args) {
CB cb = new CB("main");
// CA ca = new CA("main");
}
}
分析过程:
CB cb = new CB("main");
static{ System.out.println("in CB init"); }
CA other1 = new CA("CB.other1");
static String ivar = null;
static CB other = new CB("CA.other of static");
CA other1 = new CA("CB.other1");
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CA other2 = new CA("CB.other2");
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CB <CONSTRUCTOR> System.out.println("CB.CONSTRUCTOR by " + caller);
static {System.out.println("in CA init"); ivar = new String("2");}
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CA other2 = new CA("CB.other2");
CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
CB <CONSTRUCTOR> System.out.println("CB.CONSTRUCTOR by " + caller);
分析结果:
in CB init
CA.CONSTRUCTOR by CB.other1, ivar: null
CA.CONSTRUCTOR by CB.other2, ivar: null
CB.CONSTRUCTOR by CA.other of static
in CA init
CA.CONSTRUCTOR by CB.other1, ivar: 2
CA.CONSTRUCTOR by CB.other2, ivar: 2
CB.CONSTRUCTOR by main
final 变量
final在编译阶段完成, 如果修改final类型的常量后不要增量部署, 因为所以使用了这个变量的地方不会修改. (https://blog.youkuaiyun.com/momo_ibeike/article/details/80257552)
appendix
- dynamic call site : 指的是一次动态调用
- Call site specifier : 是一个 item, 解释如何链接到给定的 call site