类的初始化
- 当遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类没有进行初始化,则需要初始化。
这四条指令什么时候生成呢?
1)使用new关键字实例化对象的时候;
2)读取或者设置一个类的静态字段的时候(被final修饰、已在编译期把结果放入常量池的静态字段除外);
3)调用一个类的静态方法的时候 - 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要初始化。
- 当初始化一个类的时候发现其父类还没有进行初始化,则先触发其父类的初始化。
- 当虚拟机启动的时候,用户需要指定一个要执行的主类,虚拟机先初始化这个主类。
- 如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有初始化,则需要先触发其初始化。
有且仅有这5种场景是对一个类进行主动引用,除此之外都是被动引用。
被动引用例子
通过子类引用父类的静态字段,不会导致子类初始化
public class superClass {
static {
System.out.println("-------superClass");
}
public static int value = 10;
}
public class subClass extends superClass {
static {
System.out.println("------subClass");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(subClass.value);
}
}
最后输出为:
-------superClass
10
通过数组定义来引用类,不会触发此类的初始化
public class Main {
public static void main(String[] args) {
subClass[] sub = new subClass[10];
}
}
不会有输出
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
public class subClass{
public static final String message = "boke";
static {
System.out.println("------subClass");
}
}
public class yuzhiMain {
public static void main(String[] args) {
System.out.println(subClass.message);
}
}
输出为:
boke
初始化阶段
初始化阶段是执行类构造器 < clinit >( )方法的过程。
- < clinit >( )方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中语句合并产生的,静态语句块只能访问到定义在静态语句之前的变量,定义在之后的变量可以对其进行赋值但是不能访问。
public class superClass {
static {
value = 20;//赋值正常编译通过
System.out.println(value);//编译器提示“非法向前引用“
}
public static int value = 10;
}
- 虚拟机保证在子类的 < clinit >( )方法执行之前,父类的< clinit >( )方法已经执行完毕。在虚拟机中第一个被执行的< clinit >( )方法的类肯定是java.lang.Object.
- 由于父类的< clinit >( )方法先执行,所以父类中定义的静态语句块要优先于子类的变量赋值操作。
public class superClass {
public static int value = 10;
static {
System.out.println("-------superClass");
value = 20;
}
}
public class subClass extends superClass {
public static int data = value;
static {
System.out.println("------subClass");
}
}
public class yuzhiMain {
public static void main(String[] args) {
System.out.println(subClass.data);
}
}
输出为:
-------superClass
------subClass
20
- < clinit >( )方法对于类和接口来说不是必须的,如果类没有静态语句块也没对变量的赋值操作,则可以不为其生成此方法。
- 接口中也会生成< clinit >( )方法,虽然接口中不能使用静态语句块但是接口中变量初始化的赋值操作呀。
- jvm会保证一个类的< clinit >( )方法在多线程环境下被正确的加锁、同步。