主要探讨类初始化阶段的JVM规范引起的静态块初始化操作的特点
- JVM规范规定,有且只有如下五种情况必须立即对类进行初始化。
- 遇到new、getstatic、putstatic或invokestatic这四个字节码指令时。
- 使用java.lang.refect包的方法对类进行反射调用的时。
- 当初始化一个类时,如果父类还未初始化,则先触发父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄、并且这个方法句柄所对应的类没有进行过初始化时
- 类的初始化阶段
- 对于静态字段,只有直接定义这个字段的类才会被初始化。
通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证,在JVM规范中并未明确规定。对于Sun HotSpotJVM,可通过
-XX:+TraceClassLoading
观察到此操作会导致子类的加载。 - 数组类型
TypeA[] arrays = new TypeA[10]
此过程并不会触发TypeA的初始化,因为这个动作由字节码指令newarray触发。 - 常量
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类、因此不会触发定义常量的类的初始化。
- 对于静态字段,只有直接定义这个字段的类才会被初始化。
<clinit>()
方法的特性
- 类的初始化阶段是执行类构造器
<clinit>()
方法的过程 <clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态句块中的语句合并产生
1.如果一个类没有静态块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成
<clinit>()
方法
2.接口虽然没有静态块,但是有类变量的赋值操作,依然会生成<clinit>()
方法
3.如果父类没有类变量赋值时,父接口才会初始化
4.如果接口没有类变量赋值,则接口的实现类也不会执行接口的<clinit>()
- 类的初始化阶段是执行类构造器
编译器收集的顺序时由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,在前面的静态语句块可以赋值但是不能访问。如下所示,(1)能够正常编译通过,但是(2)在eclipse中会有如下提示
Cannot reference a field before it is defined
public class Test { static { i = 0;//(1) System.out.println(i);//(2) } static int i = 1; }
<clinit>()
方法不需要显示的调用父类构造器,虚拟机会保证先执行父类<clinit>()
完之后再执行子类该方法。1.在虚拟机中第一个执行的
<clinit>()
的类一定是java.lang.Object。
2.<clinit>()
执行静态块,优先执行父类<clinit>()
,因此父类的静态块要优于子类执行。- JVM为了保证
<clinit>()
方法在多线程环境中使用,使用了加锁与同步,如果多个线程同时去初始化一个类,那么一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()
方法完毕。
为了保证同一个类加载器下,一个类型只会初始化一次。执行
<clinit>()
方法的线程退出后,其他线程唤醒之后不会再次进入<clinit>()
方法。