- 前置知识: 类的生命周期
场景设计和推测
-
情况:
- 在类A中的初始化中实例化B
- 在类B的初始化中实例化A
-
类设计
-
A类:
- 静态变量
a=new B()
;静态变量a1=1
(之后在静态初始化块里赋值为2); - 实例变量
a2=11
(之后再初始化块中赋值为12); - 构造函数;
- 静态变量
-
B类:
- 静态变量
b=new A()
;静态变量b1=3
(之后在静态初始化块里赋值为4); - 实例变量
b2=21
(之后再初始化块中赋值为22); - 构造函数;
- 静态变量
-
- 猜想执行结果: 由于类初始化之后类实例化,所以A类初始化需要B实例化,B实例化又需要A初始化,造成循环依赖,最终结果为死锁
-
打点位置:
- 类加载结束点(text: Loaded Main2 from file)
- 类初始化开始点/结束点(text: Class A2 init)
- 实例初始化开始点/结束点(text: Instance A2 init)
- 构造函数结束点(text: Instance A2 new)
场景代码
class A2 { static { System.out.println("Class A2 init start"); } static B2 a = new B2(); static int a1 = 1; { System.out.println("Instance A2 init start. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2)); } public int a2 = 11; static { a1 = 2; System.out.println("Class A2 init end. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2)); } { a2 = 12; System.out.println("Instance A2 init end. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2)); } public A2() { System.out.println("Instance A2 new. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2)); }}class B2 { static { System.out.println("Class B2 init start"); } static A2 b = new A2(); static int b1 = 3; { System.out.println("Instance B2 init start. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2)); } public int b2 = 21; static { b1 = 4; System.out.println("Class B2 init end. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2)); } { b2 = 22; System.out.println("Instance B2 init end. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2)); } public B2() { System.out.println("Instance B2 new. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2)); }}class Main2 { static public void main(String... args) { System.out.println("A2 a=" + A2.a); System.out.println("A2 a1=" + A2.a1); System.out.println("A2 a2=" + B2.b.a2); System.out.println("B2 b=" + B2.b); System.out.println("B2 b1=" + B2.b1); System.out.println("B2 b2=" + A2.a.b2); }}
执行结果分析
程序输出结果:
1. [Loaded Main2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]2. [Loaded A2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]3. Class A2 init start4. [Loaded B2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]5. Class B2 init start6. Instance A2 init start. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE7. Instance A2 init end. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE8. Instance A2 new. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE9. Class B2 init end. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE10. Instance B2 init start. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE11. Instance B2 init end. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE12. Instance B2 new. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE13. Class A2 init end. a=B2@610455d6 a1=2 a.b2=22 b=A2@61bbe9ba b1=4 b.a2=1214. A2 a=B2@610455d615. A2 a1=216. A2 a2=1217. B2 b=A2@61bbe9ba18. B2 b1=419. B2 b2=22
把它转化为下面的表格,更加清晰地描述A/B各个阶段执行的过程:
A | B |
---|---|
A类加载完成 | |
A类初始化 - 开始 | |
B类加载完成 | |
B类初始化 - 开始 | |
A类实例初始化 - 开始 | |
A类实例初始化 - 结束 | |
A类实例构造函数执行完成 | |
B类初始化 - 结束 | |
B类实例初始化 - 开始 | |
B类实例初始化 - 结束 | |
B类实例构造函数执行完成 | |
A类初始化 - 结束 |
可以看到在A类初始化的过程中,A类被实例化了(并且该阶段正常结束了),也就是说类的初始化阶段并不是原子的/排他的.
如在本例中,A类实例化阶段的结束早于其类初始化阶段,A类实例化完成时,A类的静态变量还未被初始化.Reference.1中已经描述了这种情况:
_加载/验证/准备/初始化和卸载这5个阶段的顺序是确定的_,类的加载过程必须按照这种顺序按部就班的开始...注意,这里笔者写的是按部就班的"开始",而不是按部就班的"进行"或"完成",强调这点是因为这些阶段通都是相互交叉混合式进行的,通常会在一个阶段执行的过程中调用/激活另外一个阶段
< <深入理解java虚拟机> >P210 深入理解java虚拟机>
总结
- 类的循环初始化不会引起死锁
- 5个阶段的开始是有顺序的,结束则不一定
- 阶段不是排他的/临界的
-
循环初始化可能引起意料之外的情况,尽量避免
- eg.类在初始化过程中修改另一个类的变量,导致另一个类得到了意料之外的初始值
Reference
- 深入理解Java虚拟机