Java虚拟机(JVM)的类加载机制是Java语言运行时的核心组成部分。它负责将Java类的描述信息从Class文件加载到内存中,并进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。类加载的时机是指触发类加载器去加载一个类到内存中的具体条件。本文将详细介绍JVM类加载的时机。
一、主动引用的触发条件
在JVM中,当程序首次主动使用一个类时,会触发该类的加载和初始化。主动引用的触发条件包括以下几种情况:
-
创建类的实例:
当使用new
关键字创建类的新实例时,如果该类尚未被加载和初始化,则会触发类加载过程。 -
访问类的静态变量或对其进行赋值:
当访问类的静态变量(除了被final
修饰并已在编译期赋值的常量外),或者对静态变量进行赋值时,会触发类的加载和初始化。 -
调用类的静态方法:
当调用类的静态方法时,同样会触发类的加载和初始化。 -
使用反射:
当使用java.lang.reflect
包中的方法对类进行反射调用时,如Class.forName()
、Constructor.newInstance()
等,如果类还没有被加载和初始化,则会先触发其加载和初始化。 -
初始化子类:
当初始化一个子类时,如果其父类还没有被初始化,则会先触发父类的加载和初始化。 -
JVM启动时的主类:
JVM启动时,会初始化包含main()
方法的那个主类。 -
动态代理和动态类型语言支持:
在使用动态代理或JDK 1.7及以后版本引入的动态类型语言支持时,可能会触发相关类的加载和初始化。例如,当一个java.lang.invoke.MethodHandle
实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic等类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化时,需要先触发其加载和初始化。 -
接口中的默认方法:
当一个接口中定义了JDK 8新加入的默认方法(被default
关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
二、被动引用的非触发条件
除了上述主动引用的触发条件外,其他引用都不会触发类的加载和初始化,这些被称为被动引用。以下是一些被动引用的例子:
-
通过子类的引用访问父类的静态变量:
这种情况下,只会触发父类的加载和初始化,而不会触发子类的加载和初始化。 -
通过数组定义引用类:
仅仅通过定义数组来引用类,并不会触发该类的加载和初始化。例如,Parent[] array = new Parent[10];
这行代码不会触发Parent
类的加载和初始化。 -
访问类中
final
修饰的静态常量:
如果常量在编译期间已经存入常量池,那么访问这个常量本质上并没有直接引用到定义该常量的类,因此不会触发该类的加载和初始化。
三、类加载的完整过程
需要注意的是,类加载的完整过程包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化(Initialization)五个阶段。其中,加载、验证、准备三个阶段通常是在初始化之前完成的,并且是由类加载器按照双亲委派模型进行的。类加载机制遵循“懒加载”原则,即只有在真正需要的时候才会加载相应的类。
四、总结
JVM类加载的时机是Java语言运行时的重要特性之一。了解类加载的时机有助于深入理解Java程序的执行过程,并优化程序的性能。在实际开发中,可以根据业务需求和性能要求来选择合适的类加载策略,以实现更高效、更灵活的程序运行。