JVM 触发类加载的条件有哪些?

目录

一、类加载生命周期

二、主动引用

2.1、创建类的实例

2.2、访问类的静态字段或静态方法

2.3、反射

2.4、初始化类的子类时,先初始化父类

[2.5、虚拟机启动时,初始化 main 方法所在的类](#2.5、虚拟机启动时,初始化 main 方法所在的类)

2.6、动态语言支持

三、被动引用

3.1、通过子类引用父类的静态字段

3.2、访问编译期常量

3.3、通过数组定义类引用


Java 虚拟机(JVM)中,类的加载并不是随意发生的,而是由特定的触发条件 决定的。什么时候加载?什么时候初始化?

这是我们必须要搞清楚的问题,尤其在复杂的应用中,弄懂类加载的时机能帮助我们避免一些潜在的性能问题和运行时错误。

在本节中,我们将详细探讨类加载的时机、主动和被动引用的区别,以及常见的类加载触发条件。

一、类加载生命周期

类加载的生命周期包括:加载(Loading)链接(Linking)初始化(Initialization) 。而其中,初始化阶段是决定类是否被真正加载的关键。

JVM 在什么时候启动类加载过程呢?

主要分为主动引用被动引用两种情况。我们分别看看这两种情况在什么条件下会触发类加载。

二、主动引用

主动引用 是指程序显式地使用某个类,从而触发类的加载和初始化。根据《Java 虚拟机规范》,以下六种情况会触发类的主动引用,也就是触发类加载的条件!

2.1、创建类的实例

当你使用 new 关键字创建一个类的实例时,JVM 会立即加载并初始化该类。

java 复制代码
// 触发 MyClass 的加载和初始化
MyClass obj = new MyClass(); 

初始化流程

  1. 分配内存给 MyClass 的实例对象。

  2. 加载 MyClass 类的字节码,并执行静态代码块和静态变量赋值操作。

2.2、访问类的静态字段或静态方法

访问类的静态字段或静态方法时,也会触发类的加载和初始化。

java 复制代码
// 触发 MyClass 的加载
System.out.println(MyClass.staticVar);  
// 触发 MyClass 的加载
MyClass.staticMethod();                

常量不会触发类加载 :如果静态字段是 final 修饰的常量,它在编译期已存入常量池,因此不会触发类加载。

java 复制代码
System.out.println(MyClass.FINAL_CONSTANT);  // 不触发类加载

2.3、反射

通过反射调用类时,也会触发类加载。

java 复制代码
Class<?> clazz = Class.forName("com.example.MyClass");  // 触发 MyClass 的加载

2.4、初始化类的子类时,先初始化父类

当初始化一个类时,如果它的父类尚未初始化,JVM 会先初始化父类。

java 复制代码
public class Parent {
    static {
        System.out.println("父类初始化");
    }
}

public class Child extends Parent {
    static {
        System.out.println("子类初始化");
    }
}

// 先输出"父类初始化",再输出"子类初始化"
Child obj = new Child();  

2.5、虚拟机启动时,初始化 main 方法所在的类

虚拟机启动时,main 方法所在的类是程序的入口类,会被优先加载和初始化。

java 复制代码
public static void main(String[] args) {
    System.out.println("主类加载");
}

2.6、动态语言支持

在 Java 7 引入的 java.lang.invoke 包中,当 MethodHandle 最终指向的类需要初始化时,也会触发类的加载。

java 复制代码
MethodHandle handle = MethodHandles.lookup().findStatic(MyClass.class, "staticMethod", MethodType.methodType(void.class));
handle.invoke();  // 可能触发 MyClass 的加载

三、被动引用

被动引用不触发类加载。

与主动引用相对,被动引用是指访问类的某些特性时不会触发类的加载和初始化。以下是几种典型的被动引用场景。

3.1、通过子类引用父类的静态字段

如果子类只引用父类的静态字段,JVM 只会初始化父类,而不会初始化子类。

示例

java 复制代码
// 只触发 Parent 的加载,不触发 Child 的加载
System.out.println(Child.staticVar);  

3.2、访问编译期常量

访问 final 修饰的编译期常量,不会触发类的加载。

java 复制代码
// 不触发 MyClass 的加载
System.out.println(MyClass.FINAL_CONSTANT);  

3.3、通过数组定义类引用

通过数组引用一个类,不会触发该类的加载。

java 复制代码
// 不触发 MyClass 的加载
MyClass[] array = new MyClass[10];  

最后,为什么需要关注类加载的时机?

  • 避免类的过早加载:过早加载可能导致不必要的内存消耗,尤其在大型应用中。

  • 延迟加载(Lazy Loading):通过延迟加载,可以在真正需要时才加载类,减少启动时间。

  • 减少类加载冲突:在模块化或插件化的应用中,合理安排类加载顺序有助于避免类冲突和类加载死锁问题。

相关推荐
用户84913717547166 小时前
生产级故障排查实战:从制造 OOM 到 IDEA Profiler 深度破案
java·jvm
爱学java的ptt8 小时前
jvm笔记
jvm·笔记
DKPT1 天前
ZGC和G1收集器相比哪个更好?
java·jvm·笔记·学习·spring
低客的黑调1 天前
为你的项目选择一个适合的[垃圾收集器]
java·jvm·算法
xu_yule1 天前
Linux_14(多线程)线程控制+C++多线程
java·开发语言·jvm
豆奶特浓61 天前
Java面试生死局:谢飞机遭遇在线教育场景,从JVM、Spring Security到AI Agent,他能飞吗?
java·jvm·微服务·ai·面试·spring security·分布式事务
Boop_wu1 天前
[Java EE] 多线程进阶(JUC)(2)
java·jvm·算法
3***31211 天前
java进阶1——JVM
java·开发语言·jvm
打工人你好3 天前
如何设计更安全的 VIP 权限体系
java·jvm·安全
unclecss3 天前
把 Spring Boot 的启动时间从 3 秒打到 30 毫秒,内存砍掉 80%,让 Java 在 Serverless 时代横着走
java·jvm·spring boot·serverless·graalvm