初始化

进行准备阶段时,变量已经赋过一次系统要求的初始零值。

初始化阶段就是执行类构造器<clinit>()方法的过程。

<clinit>()方法是Javac编译器的自动生成物。

<clinit>()方法

  • <clinit>()方法由编译器自动收集类中的所有类变量的赋值动作和静态代码语句块

    中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,

    静态语句块中只能访问静态语句块之前的变量,定义在它之后的变量可以在前边的

    静态语句块赋值,但是不能访问。

    public class Test {
        static {
            i = 0; // 给变量赋值可以正常编译通过
            System.out.println(i); // 编译时会提示 “非法前向引用”
        }
        static int i = 1;
    }
    
  • <clinit>()方法与类的构造函数<init>()不同,不需要显示地调用父类构造器,Java虚拟机

    会保证子类的<clinit>()方法执行前,父类的<clinit>()方法执行完毕。因此Java虚拟机中

    第一个被执行的<clinit>()方法的类型肯定是java.lang.Object

  • 由于父类的<clinit>()方法,也意味着父类中定义的静态语句块要优先于子类的变量赋值操作。

    class Parent {
        public static int A = 1;
        static {
            A = 2;
        }
    }
    class Sub extends Parent {
        public static int B = A;
    }
    public static void main(String[] args) {
        System.out.println(Sub.B); // 打印2
    }
    
  • <clinit>()方法对于类或接口来说并不是必需的,如果一个类没有静态语句块也没有对类变量的赋值操作,

    那么编译器可以不为这个类生成<clinit>()方法。

  • 接口中不能使用静态方法块,但仍然有变量初始化的赋值操作,因此也能生成<clinit>()方法。

    但接口与类不同,接口的<clinit>()方法执行前不需要父接口的<clinit>()方法执行。只有当

    父接口中定义的变量被使用时父接口才会被初始化。此外接口的实现类在初始化时也一样不会执行

    接口的<clinit>()方法。

  • Java虚拟机必须保证一个类的<clinit>()方法在多线程环境下被正确加锁同步,在同一个类加载器下,

    一个类型只会被初始化一次。

class DeadLoopClass {
    static {
        if (true) {
            System.out.println(Thread.currentThread() + "init DeadLoopClass");
            while (true) {
            }
        }
    }
}

public static void main(String[] args) {
    Runnable script = new Runnable() {
        public void run() {
            System.out.println(Thread.currentThread() + "start");
            DeadLoopClass dlc = new DeadLoopClass();
            System.out.println(Thread.currentThread() + "run over");
        }  
    };

    Thread t1 = new Thread(script);
    Thread t2 = new Thread(script);
    t1.start(); // 线程将一直阻塞着,无法打印出 run over
    t2.start();
}