Class 初始化过程
面试题
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
System.out.println(T.count);
}
}
class T{
public static T t = new T();
public static int count = 2;
private T() {
count ++;
}
}
1. 输出的T.count 是多少? 2
2. 若 class 中2个static变量位置对调呢? 3
一个 class 文件是怎样放到内存里的?
类加载时,类的static变量的值经过了2个阶段
- 默认值阶段,(无论static变量右侧有没有赋值符号,都设置为默认值)
- 赋初始阶段,(若静态变量右侧有赋值操作,则赋初始值)
同理,对象在初始化时,成员变量也有相同的操作
1. 先默认值; 2. 再赋值为初始值
- 加载:.class 文件被加载到jvm方法区,并在堆中创建对应的class对象
- 验证:校验字节码文件的正确性
- 准备:为类的静态变量分配内存,并将其初始化为默认值
- static int num = 5; 准备阶段会初始化为0值
- final static 修饰的变量,编译的时候就会分配了
- 解析:把类中的符号引用转换为直接引用
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
在一个类或者对象的初始化过程中有一个准备阶段,会设置为默认值,我们称之为半初始化阶段,然后才是初始化
双重检查加锁时,单例类静态变量为什么需要volatile?
- 变量线程间可见
- double check singleton 会在半初始化的情况下可能出现问题,但是出现概率极其小
我们先重现 double check singleton 代码
静态变量的volatile的作用?
1. 线程间可见
2. 禁止指令重排序,volatile加在某个变量上时,这个变量的初始化过程,不会被指令重排序
public class Singleton {
private volatile static Singleton singleton;
// 其他成员变量或函数
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
// 关键位置1
synchronized (Singleton.class) {
if (singleton == null) {
// 第二次检查的目的是未了防止在关键位置1处被阻塞的线程激活后new 对象?
singleton = new Singleton();
}
}
}
return singleton;
}
}
new一个对象的过程其实是两步,第一步是preparation,值为默认值,第二步才是初始化。
new 对象为什么会有2步呢?不是new完了才会赋值给对象变量吗?
指令重排: CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。
public class T{
public static void main(String[] args) {
T t = new T(); // new T的 过程分为2步
}
}
汇编:
0 new #2 <T> // 第一步申请对象内存,成员变量设置为默认值
3 dup // 在操作数栈上将引用的地址赋值一份
4 invokespecial #3 <T.<init>> // 更具引用地址调用T的构造方法
7 astore_1 // 将整个引用的值赋值给t
8 return
指令重排的情况下第4个指令和第7个指令可能会互换位置