Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程
和那些编译时需要连接工作的语言不同,在Java语言里,类型的加载,连接和初始化过程都是在程序 运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为java应用程序提供比较高的灵活性。
当我们使用到某个类的时候,如果这个类还未从磁盘上加载到内存中,JVM就会通过三步走策略(加载、连接、初始化)来对这个类进行初始化,JVM完成这三个步骤的名称,就叫做类加载或者类初始化
类加载的时机什么情况下需要开始类加载的第一个阶段——加载 ,在Java虚拟机规范中没有进行强制约束,而是交给虚拟机的具体实现来进行把握,但是对于初始化阶段,虚拟机规范严格规定了 “有且只有” 五种情况必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始),具体情况如下所示:
class文件的加载时机:
序号 内容1 遇到 new、getstatic、putstatic、或invokestatic这四条字节码指令
2 使用 java.lang.reflect 包的方法对类进行反射调用的时候
3 初始化类时,父类没有被初始化,先初始化父类
4 虚拟机启动时,用户指定的主类(包含main()的那个类)
5 当使用JDK1.7动态语言支持的时,如果一个java.lang.invoke.MethodHandle 实例最后解析的结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄锁对应的类没有进行过初始化时
关于序号1的详细解释:
使用 new 关键字实例化对象时
读取类的静态变量时(被 final修饰,已在编译期把结果放入常量池的静态字段除外)
设置类的静态变量时
调用一个类的静态方法时
注意: newarray指令触发的只是数组类型本身的初始化,而不会导致其相关类型的初始化,比如,new String[]只会直接触发 String[] 类的初始化,也就是触发对类[Ljava.lang.String的初始化,而直接不会触发String类的初始化。
生成这四条指令最常见的Java代码场景是:
对于这5种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这5种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为 被动引用。
需要特别指出的是,类的实例化和类的初始化是两个完全不同的概念:
类的实例化是指创建一个类的实例(对象)的过程;
类的初始化是指为类各个成员赋初始值的过程,是类生命周期中的一个阶段;
被动引用的三个场景:
通过子类引用父类的静态字段,不会导致子类初始化
/** * @program: jvm * @ClassName Test1 * @Description:通过子类引用父类的静态字段,不会导致子类初始化 * @author: 牧小农 * @create: 2021-02-27 11:42 * @Version 1.0 **/ public class Test1 { static { System.out.println("Init Superclass!!!"); } public static void main(String[] args) { int x = Son.count; } } class Father extends Test1{ static int count = 1; static { System.out.println("Init father!!!"); } } class Son extends Father{ static { System.out.println("Init son!!!"); } }输出:
Init Superclass!!! Init father!!!对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证,在虚拟机中并未明确规定,这点取决于虚拟机的具体实现。对于Sun HotSpot虚拟机来说,可通过-XX:+TraceClassLoading参数观察到此操作会导致子类的加载。
上面的案例中,由于count字段是在Father类中定义的,因此该类会被初始化,此外,在初始化类Father的时候,虚拟机发现其父类Test1 还没被初始化,因此虚拟机将先初始化其父类Test1 ,然后初始化子类Father,而Son始终不会被初始化;
通过数组定义来引用类,不会触发此类的初始化
/** * @program: jvm * @ClassName Test2 * @description: * @author: muxiaonong * @create: 2021-02-27 12:03 * @Version 1.0 **/ public class Test2 { public static void main(String[] args) { M[] m = new M[8]; } } class M{ static { System.out.println("Init M!!!"); } }运行之后我们会发现没有输出 "Init M!!!",说明没有触发类的初始化阶段