转载:https://juejin.im/post/5c0bdc89e51d4573802ac17f
加载
在加载阶段,虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取此类的class字节码二进制流
- 将这个字节码二进制流中的静态结构转化为方法区中的运行时数据结构
- 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
对于Class对象,Java虚拟机规范并没有规定是存储在Java堆中,HotSpot虚拟机将其放在方法区内。
验证
验证作为链接过程中的第一步,大致会完成以下4个阶段的检验动作:
- 文件格式验证 该阶段主要在字节流转化为方法区中的运行时数据时,负责检查字节流是否符合Class文件的规范,保证其可以正确的被解析并存储于方法区中。后面的检查都是基于方法区的存储结构进行检验,不会再直接操作字节流。
- 元数据验证 该阶段负责分析存储于方法区的结构是否符合Java语言规范的要求,如该类是否继承了不允许继承的类(被final修饰的类)、是否包含父类等。此阶段进行数据类型的校验,保证符合不存在非法的元数据信息。
- 字节码验证 元数据验证保证了字节码中的数据类型符合语言的规范,该阶段则负责分析数据流和控制流,确定方法体的合法性,保证被校验的方法在运行时不会危害虚拟机的运行
- 符号引用验证 最后一个阶段发生在链接的解析阶段。在解析阶段,会将虚拟机中的符号引用转化为直接引用,该阶段则负责对各种符号引用进行匹配性校验,保证外部依赖真实存在,并且符合外部依赖类、字段、方法的访问性
准备
准备阶段正式为类的字段变量分配内存,并设置初始值。这些变量存储于方法区中,注意此处的变量为 类变量(被static修饰符修饰),而非实例变量。
解析
初始化
在类的class文件中,包含两个特殊的方法:<cinit>和<init>。这两个方法由编译器自动生成,分别代表类构造器和构造函数。其中构造函数可以由变成人员实现,而类构造器则由编译器自动生成。而初始化阶段则负责调用类构造器,来初始化变量和资源。
<cinit>方法由编译器自动收集类的赋值动作和静态语句块(static{}块)中的语句合并生成的,它有以下特点:- 编译器收集的顺序由源文件中语句的顺序决定,静态语句块只能访问到在它之前定义的变量,在它之后定义的变量,它只能进行赋值操作,但不能访问。
- 虚拟机保证在子类的<cinit>方法执行之前,父类的<cinit>方法已经执行完毕。因此父类中的操作对于子类都是可见的。
- 接口的<cinit>方法执行之前,不需要先执行父接口的<cinit>方法,只有父接口中定义的变量被使用时,父接口才会初始化。同时接口的实现类在初始化时也不会执行父接口的<cinit>方法。
- <cinit>方法不是必须的,如果一个类或者接口没有变量赋值和静态语句块,则编译器可以不生成<cinit>方法。
- 虚拟机会保证<cinit>方法在多线程中被正确的加锁、同步。如果多个线程同时去初始化一个类,那么只有一个线程去执行<cinit>方法,其他线程会被阻塞。
总结
正确掌握类加载的过程,可以对平常编程中的各种问题有更深入的了解。下面通过一个违背感觉的实例,来感受下掌握类加载过程的重要性。
class SingleTon { private static SingleTon singleTon = new SingleTon(); public static int count1; public static int count2 = 0; private SingleTon() { count1++; count2++; } public static SingleTon getInstance() { return singleTon; } } public class Test { public static void main(String[] args) { SingleTon singleTon = SingleTon.getInstance(); System.out.println("count1=" + singleTon.count1); System.out.println("count2=" + singleTon.count2); } }
上面代码的执行结果为:
count1=1
count2=0
按照初始化的触发条件,我们可知在main方法调用getInstance时,会进行SingleTon的类加载过程。
在准备阶段,会对变量添加初始值,此时singleTon=null,count1=0,count2=0。
在初始化阶段,先执行new SingleTon(),此时count1=count2=1,然后由于count1无赋值操作,所以count1=1。count2赋值为0,所以结果为count1=1,count2=0。
假如将**private static SingleTon singleTon = new SingleTon();语句移动到public static int count2 = 0;**之后,则结果为count1=1,count2=1