初始化是类加载过程的最后一步,但由于比较重要,故放在前面先讲。

在前面的连接(准确来说是准备)阶段,类的变量已经被赋予默认值(如int类型为0,布尔类型为false,引用类型为null等)。而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。或者可以从另一个角度来表达:初始化阶段是执行类构造器<clini>()方法的过程。

1. <clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,收集的顺序是由语句在源文件中出现顺序所决定。静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,可以赋值,但不能访问。如下将报错:

 

2. <clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此,在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object;

3.由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作,如下将打印2

 1 package com.khlin.initialization;
 2 
 3 public class App {
 4 
 5     public static void main(String[] args) {
 6         System.out.println(Sub.b);
 7     }
 8 
 9     static class Parent {
10         public static int a = 1;
11         static {
12             a = 2;
13         }
14     }
15 
16     static class Sub extends Parent {
17         public static int b = a;
18     }
19 }

 

4.<clinit>()方法对于类或接口并不是必需的,如果类没有静态语句块,也没有赋值动作,那么编译器可以不为这个类生成<clinit>()方法;

5.接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,所以接口和类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,才会初始化。另化,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法;

6.虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。单例的initialization on demand holder模式就是利用了这一特性。如果在一个类的<clinit>()方法中有耗时很长的操作,就有可能造成多个进程阻塞。需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()之后,其他线程唤醒后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。

 

 1 package com.khlin.initialization;
 2 
 3 public class EndlessLoop {
 4 
 5     static {
 6         if (true) {
 7             System.out.println(Thread.currentThread() + " init EndlessLoop");
 8             while (true) {
 9             }
10         }
11     }
12 
13 }
 1 public static void main(String[] args) {
 2         Runnable runnable = new Runnable() {
 3 
 4             @Override
 5             public void run() {
 6                 System.out.println(Thread.currentThread() + " is running....");
 7                 EndlessLoop endlessLoop = new EndlessLoop();
 8                 System.out.println(Thread.currentThread() + " runs over");
 9             }
10         };
11 
12         Thread threadA = new Thread(runnable);
13         Thread threadB = new Thread(runnable);
14         threadA.start();
15         threadB.start();
16     }

结果会显示: