- 前置知识: 类的生命周期
场景设计和推测
-
情况:
- 在类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 start
4. [Loaded B2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]
5. Class B2 init start
6. Instance A2 init start. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
7. Instance A2 init end. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
8. Instance A2 new. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
9. Class B2 init end. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
10. Instance B2 init start. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
11. Instance B2 init end. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
12. Instance B2 new. b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
13. Class A2 init end. a=B2@610455d6 a1=2 a.b2=22 b=A2@61bbe9ba b1=4 b.a2=12
14. A2 a=B2@610455d6
15. A2 a1=2
16. A2 a2=12
17. B2 b=A2@61bbe9ba
18. B2 b1=4
19. 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
总结
- 类的循环初始化不会引起死锁
- 5个阶段的开始是有顺序的,结束则不一定
- 阶段不是排他的/临界的
-
循环初始化可能引起意料之外的情况,尽量避免
- eg.类在初始化过程中修改另一个类的变量,导致另一个类得到了意料之外的初始值
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。