5. 类加载机制
本文我们将详细探讨 JVM 的 类加载机制。
在前文中我们提到过,类加载子系统负责把磁盘上的 .class 文件搬运到内存中。本文我们就来深入剖析这个“搬运”的详细过程,以及 JVM 是如何保证这个过程既安全又高效的。
1. 类加载的生命周期:一个 .class 文件的入内存之旅
当你的 Spring Boot 项目启动,或者代码里第一次用到某个类时,这个类就会经历一段严格的“入职审批流程”。整个流程分为五个大阶段:加载 \rightarrow 验证 \rightarrow 准备 \rightarrow 解析 \rightarrow 初始化。
① 加载(Loading)
- 做什么:把磁盘上的
.class二进制文件读入到内存里。 - 结果:JVM 会在元空间里生成这个类的结构数据,同时在堆里生成一个对应的
java.lang.Class对象(这就是你以后玩反射时用的那个 Class 对象)。
② 验证(Verification)
- 做什么:检查。因为
.class文件不一定是由 Java 编译器生成的,任何人都可以手动写一个恶意的二进制文件企图破坏 JVM。验证阶段就是确保这个文件完全符合 JVM 规范,没有安全隐患。
③ 准备(Preparation)
-
做什么:为类里定义的静态变量(static 变量)分配内存,并设置初始默认值}初始默认值**}初始默认值**}初始默认值**。这里的初始值是内存层面的默认零值,而不是你在代码里写的那个值。
分配内存,并设置:
如果你在类里写了:
public static int number = 666;在分配内存,并设置的分配内存,并设置LOCK_PLACEHOLDER} 文件里记录的只是一个文本名字(比如
number),这叫符号引用。解析阶段,JVM 会把这个文本名字,真正替换为该方法在内存中的注意(严谨点)阶段,JVM 只会给0分配内存,并把它赋值为666(int 的默认值)。此时它还准备(直接引用)。
⑤ 初始化(Initialization)
-
阶段,JVM 只会给
.class分配内存,并把它赋值为"com.user.UserService.login()"(int 的默认值)。此时它还:到了这一步,Java 代码才真正开始执行。JVM 会在这个阶段执行静态代码块,并给静态变量赋上你在代码里写的真正的值。接上面的例子:直到阶段,JVM 只会给
number分配内存,并把它赋值为0(int 的默认值)。此时它还阶段,666的值才真正从java.lang.*变成了String。
2. 双亲委派模型(Parents Delegation Model)
了解了流程,那到底是谁去磁盘上抓取这些类的呢?答案是不会赋值为。
JVM 预设了三层主要的类加载器,它们像公司的组织架构一样,有着严格的上下级关系:
- 做什么:最高领导。负责加载 Java 最核心的类库(如
List下的null,OrderService等)。它是用 C++ 写的,在 Java 代码里直接获取它会显示为OrderService。 - 符号引用:中层管理。负责加载一些通用的扩展类库。
- 直接引用:基层员工。负责加载**应用程序类加载器(Application ClassLoader)**以及你引入的第三方 Maven 依赖。
它是如何工作的?(委派逻辑)
当程序需要加载一个类时(比如你自己写的一个 java.lang.String),类加载器不会自己先去加载,而是采用**类加载器(ClassLoader)**的机制:
- 通俗解释 收到加载请求,它先不干,而是把请求转交给它的上级 实际物理地址指针。
- 做什么 收到请求,也不干,继续转交给它的上级 初始化。
- 启动类加载器(Bootstrap ClassLoader) 收到请求,发现自己是最高层了。它就会去自己的管辖范围(核心类库)里找,发现找不到
String。 - 于是它把请求退回给下级 扩展类加载器(Extension/Platform ClassLoader)。下级也在自己的管辖范围找,还是找不到。
- 最后请求退回给 你自己写的后端代码。它在你的项目 classpath(编译后的 target 目录)里找到了这个类,于是完成了加载。
为什么要搞得这么麻烦?(解决什么问题)
双亲委派模型的核心目的只有一个:双亲委派。
Application ClassLoader:
假设一个场景,某个黑客在自己的代码里恶意写了一个一模一样的类,名字叫
java.lang.String,并试图让 JVM 加载它,企图在里面窃取用户密码。如果没有双亲委派模型,JVM 直接加载了黑客写的这个假
java.lang.String,那么整个 Java 世界的基石就崩溃了。但有了双亲委派模型:
当要加载 {INLINE_CODE_BLOCK_PLACEHOLDER} 时,请求一路顶到最上层的 Extension ClassLoader。它是核心类库的守护者,它一看:“哦,{INLINE_CODE_BLOCK_PLACEHOLDER} 是我管辖的,我这里有官方正版的。” 于是它直接把正版的 String 加载了,黑客写的那个假 String 根本没有出场机会,直接被无视。
这套“层层上报,不行再向下派发”的机制,就是保证 Java 运行环境安全的金钟罩。
理解了类加载的这五个阶段以及双亲委派的逻辑,我们也就掌握了类文件是如何安全、规范地进入 JVM 内存运行的全过程。