Class 初始化过程

面试题

  1. public class T001_ClassLoadingProcedure {
  2. public static void main(String[] args) {
  3. System.out.println(T.count);
  4. }
  5. }
  6. class T{
  7. public static T t = new T();
  8. public static int count = 2;
  9. private T() {
  10. count ++;
  11. }
  12. }
  13. 1. 输出的T.count 是多少? 2
  14. 2. class 2static变量位置对调呢? 3

一个 class 文件是怎样放到内存里的?
类加载时,类的static变量的值经过了2个阶段

  1. 默认值阶段,(无论static变量右侧有没有赋值符号,都设置为默认值)
  2. 赋初始阶段,(若静态变量右侧有赋值操作,则赋初始值)

    同理,对象在初始化时,成员变量也有相同的操作
    1. 先默认值; 2. 再赋值为初始值

Untitled Diagram.svg

  1. 加载:.class 文件被加载到jvm方法区,并在堆中创建对应的class对象
  2. 验证:校验字节码文件的正确性
  3. 准备:为类的静态变量分配内存,并将其初始化为默认值
    1. static int num = 5; 准备阶段会初始化为0值
    2. final static 修饰的变量,编译的时候就会分配了
  4. 解析:把类中的符号引用转换为直接引用
  5. 初始化:对类的静态变量初始化为指定的值,执行静态代码块

在一个类或者对象的初始化过程中有一个准备阶段,会设置为默认值,我们称之为半初始化阶段,然后才是初始化

双重检查加锁时,单例类静态变量为什么需要volatile?

  1. 变量线程间可见
  2. double check singleton 会在半初始化的情况下可能出现问题,但是出现概率极其小

我们先重现 double check singleton 代码

  1. 静态变量的volatile的作用?
  2. 1. 线程间可见
  3. 2. 禁止指令重排序,volatile加在某个变量上时,这个变量的初始化过程,不会被指令重排序
  4. public class Singleton {
  5. private volatile static Singleton singleton;
  6. // 其他成员变量或函数
  7. private Singleton() {}
  8. public static Singleton getSingleton() {
  9. if (singleton == null) {
  10. // 关键位置1
  11. synchronized (Singleton.class) {
  12. if (singleton == null) {
  13. // 第二次检查的目的是未了防止在关键位置1处被阻塞的线程激活后new 对象?
  14. singleton = new Singleton();
  15. }
  16. }
  17. }
  18. return singleton;
  19. }
  20. }

new一个对象的过程其实是两步,第一步是preparation,值为默认值,第二步才是初始化。
double check singleton.svg
new 对象为什么会有2步呢?不是new完了才会赋值给对象变量吗?
指令重排: CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

  1. public class T{
  2. public static void main(String[] args) {
  3. T t = new T(); // new T的 过程分为2步
  4. }
  5. }
  6. 汇编:
  7. 0 new #2 <T> // 第一步申请对象内存,成员变量设置为默认值
  8. 3 dup // 在操作数栈上将引用的地址赋值一份
  9. 4 invokespecial #3 <T.<init>> // 更具引用地址调用T的构造方法
  10. 7 astore_1 // 将整个引用的值赋值给t
  11. 8 return
  12. 指令重排的情况下第4个指令和第7个指令可能会互换位置