大家好,我是码哥,《Redis 高手心法》作者。
Java 虚拟机(JVM)中,类的加载并不是随意发生的,而是由特定的触发条件 决定的。什么时候加载?什么时候初始化?
这是我们必须要搞清楚的问题,尤其在复杂的应用中,弄懂类加载的时机能帮助我们避免一些潜在的性能问题和运行时错误。
在本节中,我们将详细探讨类加载的时机、主动和被动引用的区别,以及常见的类加载触发条件。
在进入正文前,介绍下我的《Java 面试高手心法 58 讲》专栏,帮助你在现在就业压力巨大的情况下比别人多一个杀手锏,拿下心仪 offer。内容涵盖 Java 基础、Java 高级进阶、Redis、MySQL、消息中间件、微服务架构设计 等面试必考点、面试高频点。
丢掉你收藏的那些所谓的「面试宝典」,因为它们大多数深度不够,甚至内容还有错误,你只会看完就忘,还浪费时间。这也是为何每次面试你都回答不好,找不到好工作的原因。
正文开始......
类加载生命周期
类加载的生命周期包括:加载(Loading) 、链接(Linking) 和 初始化(Initialization) 。而其中,初始化阶段是决定类是否被真正加载的关键。
JVM 在什么时候启动类加载过程呢?
主要分为主动引用 和被动引用两种情况。我们分别看看这两种情况在什么条件下会触发类加载。
主动引用
主动引用 是指程序显式地使用某个类,从而触发类的加载和初始化。根据《Java 虚拟机规范》,以下六种情况会触发类的主动引用,也就是触发类加载的条件!
1. 创建类的实例
当你使用 new
关键字创建一个类的实例时,JVM 会立即加载并初始化该类。
go
// 触发 MyClass 的加载和初始化
MyClass obj = new MyClass();
初始化流程:
-
分配内存给
MyClass
的实例对象。 -
加载
MyClass
类的字节码,并执行静态代码块和静态变量赋值操作。
2. 访问类的静态字段或静态方法
访问类的静态字段或静态方法时,也会触发类的加载和初始化。
go
// 触发 MyClass 的加载
System.out.println(MyClass.staticVar);
// 触发 MyClass 的加载
MyClass.staticMethod();
常量不会触发类加载 :如果静态字段是 final
修饰的常量,它在编译期已存入常量池,因此不会触发类加载。
go
System.out.println(MyClass.FINAL_CONSTANT); // 不触发类加载
3. 反射
通过反射调用类时,也会触发类加载。
go
Class<?> clazz = Class.forName("com.example.MyClass"); // 触发 MyClass 的加载
4. 初始化类的子类时,先初始化父类
当初始化一个类时,如果它的父类尚未初始化,JVM 会先初始化父类。
go
public class Parent {
static {
System.out.println("父类初始化");
}
}
public class Child extends Parent {
static {
System.out.println("子类初始化");
}
}
// 先输出"父类初始化",再输出"子类初始化"
Child obj = new Child();
5. 虚拟机启动时,初始化 main
方法所在的类
虚拟机启动时,main
方法所在的类是程序的入口类,会被优先加载和初始化。
go
public static void main(String[] args) {
System.out.println("主类加载");
}
6. 动态语言支持
在 Java 7 引入的 java.lang.invoke
包中,当 MethodHandle
最终指向的类需要初始化时,也会触发类的加载。
go
MethodHandle handle = MethodHandles.lookup().findStatic(MyClass.class, "staticMethod", MethodType.methodType(void.class));
handle.invoke(); // 可能触发 MyClass 的加载
被动引用:不触发类加载
与主动引用相对,被动引用是指访问类的某些特性时不会触发类的加载和初始化。以下是几种典型的被动引用场景。
1. 通过子类引用父类的静态字段
如果子类只引用父类的静态字段,JVM 只会初始化父类,而不会初始化子类。
示例
go
// 只触发 Parent 的加载,不触发 Child 的加载
System.out.println(Child.staticVar);
2. 访问编译期常量
访问 final
修饰的编译期常量,不会触发类的加载。
go
// 不触发 MyClass 的加载
System.out.println(MyClass.FINAL_CONSTANT);
3. 通过数组定义类引用
通过数组引用一个类,不会触发该类的加载。
go
// 不触发 MyClass 的加载
MyClass[] array = new MyClass[10];
码哥,为什么需要关注类加载的时机?
-
避免类的过早加载:过早加载可能导致不必要的内存消耗,尤其在大型应用中。
-
延迟加载(Lazy Loading):通过延迟加载,可以在真正需要时才加载类,减少启动时间。
-
减少类加载冲突:在模块化或插件化的应用中,合理安排类加载顺序有助于避免类冲突和类加载死锁问题。
最后(Ending)
最后,也向大家介绍下我的新书《Redis 高手心法》。本书基于 Redis 7.0 版本,将复杂的概念与实际案例 相结合,以简洁、诙谐、幽默的方式揭示了Redis的精髓。本书不仅是学习 Redis 的必备指南,更是驾驭 Redis 强大功能的秘籍。
无论你是初学者还是经验丰富的开发者,都会在阅读本书的过程中得到启发与收获。如果你希望站在Redis的顶峰,那么《Redis高手心法》绝对是你不可或缺的利器!
往期推荐
<>
面试提问:JVM 垃圾回收算法有哪些?CMS、G1、ParNew、Serial、Parallel 原理是什么?
<>
<>
JVM 为什么需要类加载机制?深入浅出 JVM 类加载原理
<>
<>
堆、栈、方法区到底是什么?一文带你搞懂 JVM 运行时数据区内存模型!
<>
<>
什么是 JVM?JVM 为什么是开发者必须了解的核心技术?
<>
作者简介
《Redis 高手心法》 作者,InfoQ 签约作者、51CTO Top 红人。拥有 10 年互联网工作经验,作为后端架构师,擅长 Redis、Tomcat、Spring、Kafka、MySQL 技术,对分布式微服务架构有深入了解。
点击卡片,关注我,学技术
我是码哥,我们下期见。