类的初始化
类的初始化时类加载的最后一步,此时才开始执行 Java 代码(字节码)。
初始化阶段是开始执行 clinit<>() 方法的过程。
1、clinit<>():由编译器收集类中的类变量赋值操作和 static 代码块中的语句合并产生的,收集顺序与源文件中出现的顺序决定;static 语句块只能访问到在 static 语句块之前定义的变量,定义在static 语句块之后的变量,static 语句块只能赋值,不能访问(编译器会提示:Inlegal forward reference)。
2、虚拟机会保证在子类的 clinit<>() 方法之前,父类的 clinit<>() 方法已经执行完毕,所以虚拟机中第一个执行 clinit<>() 方法的类一定是 java.lang.Object。
3、父类的 clinit<>() 方法优先于子类的 clinit<>() 方法,则父类的静态语句块优先于子类的变量赋值操作。
4、<clinit>() 对于类或接口来说不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,编译器可以不为这个类生成 <clinit>() 方法。
5、接口中不能使用静态语句块,但仍有变量初始化的赋值操作,接口也会生成 <clinit>() 方法,接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法,只有当使用父接口中定义的变量时,父接口才会初始化,接口中实现的类在初始化时也不会执行接口的 <clinit>() 方法。
6、虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁,同步(如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕。),如果在一个类中的 <clinit>() 方法中有很多耗时很长的操作,就可能造成多个线程阻塞(其他线程虽然会被阻塞,但如果执行 <clinit>() 方法的那条线程退出 <clinit>() 方法后,其他线线程唤醒之后不会再次进入 <clinit>() 方法。即:同一个类加载器下,一个类型只会初始化一次),在实际应用中阻塞是很隐蔽的。
init() 和 clinit<>() 区别(来自 stack overflow):
init():实例构造函数(构造函数之一),非静态字段的初始化;
clinit<>():静态字段、静态代码块的初始化。

浙公网安备 33010602011771号