Java类加载时机与过程

文章部分内容引用于:
从一道面试题来认识java类加载时机与过程

增加了一些自己的理解。


由一个题目引出

写出下面代码的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;

private SingleTon() {
count1++;
count2++;
}

public static SingleTon getInstance() {
return singleTon;
}
}

public class Main {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}

看起来像简单的单例类,但要注意count1与count2都是静态变量。
错误的结果:

1
2
3
count1=1

count2=1

正确答案:

1
2
3
count1=1

count2=0

为什么呢?

分析

静态变量顺序初始化,先初化singleTon,调用count1++,count2++。
再初始化count2 = 0。于是count2在构造函数里面累加被重置了。

如果把声明顺序调换下。
比如

1
2
3
public static int count1;
public static int count2 = 0;
private static SingleTon singleTon = new SingleTon();

则结果会是

1
2
count1=1
count2=1

产生上面1,0的原因就是 类加载的时 分配内存并初始化默认值 与 类初始化时 静态变量赋值和执行静态代码块,要分开看。

类的加载过程

类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。

何时开始类的初始化

主动引用

什么情况下需要开始类加载过程的第一个阶段:”加载”。虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。

  • 创建类的实例
  • 访问类的静态变量
  • 访问类的静态方法
  • 反射如(Class.forName(“com.xx.Main”))
  • 当初始化一个类时,发现其父类还未初始化,则先触发父类的初始化
  • 虚拟机启动时,定义了main()方法的那个类先初始化

以上情况称为称对一个类进行“主动引用”,除此种情况之外,均不会触发类的初始化,称为“被动引用”

被动引用

被动引用例子:

  • 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。。对于静态字段,只有直接定义这个字段的类才会被初始化.
  • 通过数组定义来引用类,不会触发类的初始化
  • 访问类的常量,不会初始化类

类的加载过程

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化

更多参考