虚拟机类加载机制【初始化】
2021-08-01 本文已影响0人
云芈山人
初始化时类加载的最后一个阶段,在之前的阶段里,除了用到用户自定义的类加载器时参与到其中之外,其余都是虚拟机主导的。直到初始化阶段,Java虚拟机才真正执行类中编写的Java程序代码,将主导权移交给应用程序。
进行准备阶段时,变量已被赋过系统要求的初始值,而在初始化时,才会根据程序员通过代码编码制定的主观计划去初始化类变量和其它资源。也可以说:初始化阶段就是执行类构造器<clinit>()方法的过程。<clinit>()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物,但对于它的产生以及方法执行过程中各种可能会影响程序运行行为的细节都有必要了解。
- <clinit>()方法由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生,收集顺序由语句在源文件中出现顺序决定,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
public class Test{
static{
i = 0;//给变量赋值可以正常通过
System.out.print(i);//编译器会提示“非法前向引用”
}
static int i = 1;
}
- <clinit>()方法与类的构造函数(虚拟机视角下的实例构造器<init>()方法)不同,它不需要显式调用父类构造器,虚拟机会保证在子类的<clinit>()方法被执行前,父类的<clinit>()方法已经执行完毕。因此虚拟机中第一个被执行的<clinit>()一定是java.lang.Object。
- 由于父类的<clinit>()方法先执行,意味着父类中定义的静态语句块要优于子类的变量赋值操作。
public class SuperClass {
public static int A = 1;
static {
A = 2;
}
}
public class SubClass extends SuperClass {
public static int B = A;
}
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.A);//输出为2
}
}
- <clinit>()方法对于类或接口来说并不必需,若类中没有静态语句块,也没有变量的赋值操作,则编译器不产生此方法。
- 接口中不能使用静态语句块,但仍有变量赋值操作,接口也会生成<clinit>()方法。接口与类不同的是,执行接口中定义的变量被使用时,父接口才会被初始化。此外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
- Java虚拟机必须保证一个类的<clinit>()方法在多线程环境中被正确地加锁同步,如果多线程同时去初始化一个类,那么只有其中一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行完毕<clinit>()方法。如果一个类的<clinit>()方法中有耗时很长的操作,那就可能造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。
《深入理解Java虚拟机》学习笔记