这是一个经典例子,很多面试题中出现过。首先引入正常的情况:

 1package com.test.jvm.learn01;
 2
 3public class Main2 {
 4	public static void main(String[] args) {
 5		System.out.println(Counter.counter1);
 6		System.out.println(Counter.counter2);
 7	}
 8}
 9
10
11package com.test.jvm.learn01;
12
13public class Counter {
14
15	public static int counter1;
16	public static int counter2 = 0;
17	public static Counter counter = new Counter();
18
19	private Counter() {
20		counter1++;
21		counter2++;
22		System.out.println(counter1);
23		System.out.println(counter2);
24	}
25
26	public static Counter getInstance() {
27		return counter;
28	}
29}

输出结果为:

11
21
31
41

如果把第6行private static int counter2 = 0;挪到第15行,即:

 1package com.test.jvm.learn01;
 2
 3public class Counter {
 4
 5	public static int counter1;
 6	public static Counter counter = new Counter();
 7
 8	private Counter() {
 9		counter1++;
10		counter2++;
11		System.out.println(counter1);
12		System.out.println(counter2);
13	}
14
15	public static int counter2 = 0;
16
17	public static Counter getInstance() {
18		return counter;
19	}
20}

然后输出的结果:

11
21
31
40

第一种情况是正常输出,下面从类的加载和初始化上解释第二种情况的结果:

  1. main方法中调用了Counter类的静态字段Counter.counter1,所以Counter类被主动使用,触发Counter类的初始化。
  2. 在类的连接过程中的准备阶段,静态变量分配内存且赋上默认值,所以从上到下,counter1=0,counter =null,counter2 =0
  3. 然后在类的初始化过程中,从上到下依次初始化各个字段:counter1为初始化为0
  4. 在初始化counter时需要创建Counter对象,Counter的构造方法被调用,所以初始化counter后,counter1=1,counter2=1
  5. 初始化counter引用后,紧接着初始化counter2,此时counter2被显式赋值为0,所以此时,counter1=1,counter2=0
  6. 最后打印输出即为第4步的结果