1.类加载
- 遇到new、getstaic、putstatic或invokestatic这四条字节码指令,如果类没有进行过初始化,则需要先触发其初始化。这四大场景分别是:使用new关键字实例化对象、读取或者设置一个静态字段、调用类的静态方法。
- 使用java.lang.reflect包的方法对类惊醒反射调用的时候。
- 当需要初始化的类的父类没有初始化时。
- 虚拟机启动时,用户需要指定一个要执行的主类(main),虚拟机会初始化这个类。
- 使用动态语言时,如果一个java.lang.invoke.MethodHandle实例最后解析结果是方法句柄(详见JVM虚拟机详解)则需触发其初始化。
(tips:对于静态字段,只会触发直接定义这个字段的类才会被初始化;接口也有初始化过程,有且只有上面的第三种情况来初始化,由编译器自动生成<clinit>类构造器,初始化接口中所定义的成员变量。同时,类初始化要求其父类全部完成初始化,但是初始化接口时,不要求其父接口全部完成初始化,只有真正用到父接口的时候(如引用接口定义的常量)才会初始化)
一个非数组类的加载阶段是开发人员可控性最强的,因为除系统提供的加载器外,还可以用户自定义类加载器去控制字节流的获取方式。
(tips:加载阶段与连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,任然属于连接阶段,这两个阶段的开始时间仍然保持着固定的先后顺序。)
本阶段需要完成下面工作:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 通过这个字节流转换为运行时数据结构。
- 在内存中生成一个代表类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(tips:相对于类加载的其他阶段,此阶段可控性最强,可以用用户自定义的类加载器来实现。)
2.连接
共包含验证、准备、解析三个阶段。
2.1验证
Java虽然是相对安全的语言,但Class文件并不一定要求用Java源码编译而来,因此验证阶段就非常重要了,这个阶段的严谨,直接决定了Java虚拟机是否能承受恶意代码的攻击,验证阶段在类加载子系统中占了相当大的部分。
共四个阶段:****文件格式验证、元数据验证、字节码验证、符号引用验证****。
字节码验证是整个过程最复杂的阶段:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析,确保不危害虚拟机。
2.2准备
准备阶段为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存将直接使用方法区分配。这个阶段仅实例化类变量,而不包括实例变量。实例变量将会在对象实例化随着对象一起分配在Java堆中。其次,这里所说的初始值通常情况下是指数据类型的零值,准备阶段不会执行任何用户代码。用户的赋值是在程序被编译后,存放于类构造器(<clinit>)中。如果类字段的字段属性表中存在ConstantValue,那么准备阶段就会初始化为ConstantValue的值,即final修饰的变量。
2.3解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。(符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。)
3初始化
类加载的最后一步,之前的部分,除了加载用户自定义的类加载器之外,其余动作都是由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。初始化阶段是执行<clinit>()方法的过程。
- <clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中智能访问到定义在静态语句块之前的变量,定义在其之后的便令,在前面的静态语句块可以赋值但不能访问。
- <clinit>()与类的构造函数不同,他不需要显式地调用父类构造器,虚拟机保证在自雷<clinit>()方法之前,父类的<clinit>()方法已经执行完毕。
- 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类型的变量赋值操作。
- 如果类中没有静态语句块,也没有对类变量的赋值奥做,那么编译器可以不为这个类生成<clinit>()方法。
- 接口中不能使用静态语句块,但仍然由变量初始化的赋值擦偶哦,因此接口和类一样都会生成<clinit>()方法,执行接口的<clinit>()不需要先执行父接口的<clinit>()方法。只有负借口定义的变量使用时,父接口才会初始化,接口的实现类初始化时也一样不会执行接口的<clinit>()。
- 虚拟机保证,会有一个类<clinit>()方法在多线程环境找那个被正确得枷锁,同步,如果多线程同时曲初始化一个类那么只有一个类会执行这个方法,其他阻塞。