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 预设了三层主要的类加载器,它们像公司的组织架构一样,有着严格的上下级关系:

  1. 做什么:最高领导。负责加载 Java 最核心的类库(如 List 下的 null, OrderService 等)。它是用 C++ 写的,在 Java 代码里直接获取它会显示为 OrderService
  2. 符号引用:中层管理。负责加载一些通用的扩展类库。
  3. 直接引用:基层员工。负责加载**应用程序类加载器(Application ClassLoader)**以及你引入的第三方 Maven 依赖。

它是如何工作的?(委派逻辑)

当程序需要加载一个类时(比如你自己写的一个 java.lang.String),类加载器不会自己先去加载,而是采用**类加载器(ClassLoader)**的机制:

  1. 通俗解释 收到加载请求,它先不干,而是把请求转交给它的上级 实际物理地址指针
  2. 做什么 收到请求,也不干,继续转交给它的上级 初始化
  3. 启动类加载器(Bootstrap ClassLoader) 收到请求,发现自己是最高层了。它就会去自己的管辖范围(核心类库)里找,发现找不到 String
  4. 于是它把请求退回给下级 扩展类加载器(Extension/Platform ClassLoader)。下级也在自己的管辖范围找,还是找不到。
  5. 最后请求退回给 你自己写的后端代码。它在你的项目 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 内存运行的全过程。

一个敲代码的