Spring Boot 启动时,JVM 是如何工作的?

场景:你执行 java -jar myapp.jar,一个 Spring Boot 应用开始启动。

我们将沿着真实启动流程,逐个拆解 JVM 五大组件的角色。


🧩 前提:Spring Boot 程序的本质

Spring Boot 应用只是一个标准的 Java 程序 ,它有一个 main 方法:

java 复制代码
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

当你运行 java -jar myapp.jar 时,JVM 并不知道这是 Spring Boot。它只做一件事:

找到 Main-Class(比如 com.example.MyApplication),加载它,初始化它,然后调用它的 main() 方法。

Spring Boot 的所有魔法,都是在 main() 被调用之后才开始的。

而在这之前,全是 JVM 的工作


1️⃣ 类加载器子系统(Class Loader Subsystem)

✅ 它是什么?

JVM 中负责读取 .class 文件并转换为内部类表示的模块。

在 Spring Boot 启动时,主要由 Application ClassLoader(也叫 System ClassLoader)工作。

✅ 它做了什么?怎么做的?

  • myapp.jar 中读取 MANIFEST.MF,找到 Main-Class: com.example.MyApplication
  • 在 JAR 的 BOOT-INF/classes/BOOT-INF/lib/ 中查找 com/example/MyApplication.class
  • 将字节码加载进 JVM,生成 Class<MyApplication> 对象;
  • 同时加载 Spring Boot 相关类(如 SpringApplication, @SpringBootApplication 等)。

🔍 Spring Boot 使用了自定义类加载逻辑 (通过 LaunchedURLClassLoader),但底层仍基于 JVM 的类加载机制。

✅ 为什么要这么做?

  • Java 是"编译一次,到处运行",代码必须以字节码形式交给 JVM;
  • JVM 必须在调用 main() 前,完全理解这个类的结构(方法、注解、父类等)。

❌ 如果不这么做会怎样?

  • JVM 找不到 MyApplication.class → 报错:ClassNotFoundException
  • 如果类加载器不解析注解(如 @SpringBootApplication),Spring Boot 就无法知道要开启自动配置。

💡 关键点:Spring Boot 的"自动配置"能力,依赖于 JVM 能正确加载并解析类上的注解------这是类加载器的基础能力。


2️⃣ 运行时数据区(Runtime Data Areas)

✅ 它是什么?

JVM 内部的逻辑内存模型,用于分类存储程序运行时的数据。

在 Spring Boot 启动时,主要涉及:

  • 方法区(Metaspace) :存类信息(如 MyApplication 的结构、注解、方法字节码);
  • Java 虚拟机栈 :为 main() 方法分配栈帧(存局部变量 args);
  • 堆(Heap):存放 Spring 容器、Bean、配置对象等(启动后大量创建)。

✅ 它做了什么?怎么做的?

  • MyApplication.class 被加载,其元数据(类名、方法、注解)存入方法区
  • 调用 main() 时,JVM 在 上创建一个栈帧,存放 args 参数;
  • 执行 SpringApplication.run(...) 时,创建 SpringApplication 对象,存入
  • 后续创建的 Bean(如 UserService, DataSource)也都放在中。

✅ 为什么要这么做?

  • 分类管理:类信息几乎不变,适合放方法区;对象动态创建,适合放堆;方法调用需要隔离,适合用栈。
  • 效率与安全:避免不同数据互相干扰(比如方法局部变量不会覆盖类结构)。

❌ 如果不这么做会怎样?

  • 如果类信息放堆里:可能被 GC 错误回收,导致 NoClassDefFoundError
  • 如果没有栈:递归调用或嵌套方法无法管理局部变量,程序逻辑混乱;
  • 如果所有对象放一起:无法对"长期存活的 Spring 容器"和"临时对象"做不同 GC 策略,性能极差。

💡 Spring Boot 启动时会加载上千个类,方法区必须高效存储这些元数据,否则启动慢或 OOM。


3️⃣ 执行引擎(Execution Engine)

✅ 它是什么?

JVM 中真正执行字节码 的模块。它逐条解释或编译执行 main() 及其调用的方法。

✅ 它做了什么?怎么做的?

  • main() 的第一条字节码开始执行;
  • 调用 SpringApplication.run() → 执行 Spring Boot 的启动逻辑:
    • 解析 @SpringBootApplication
    • 扫描 Bean;
    • 创建 ApplicationContext;
    • 初始化内嵌 Tomcat(如果 Web 应用);
  • 所有这些动作,最终都转化为字节码指令的执行 (如 invokestatic, new, putfield)。

🔍 执行引擎可能先解释执行 (慢但快启动),后续对热点代码(如 Controller)JIT 编译为机器码(快)。

✅ 为什么要这么做?

  • 字节码是平台无关的,但计算机只能执行本地指令;
  • 执行引擎充当"翻译+执行者",屏蔽底层差异,实现"一次编译,到处运行"。

❌ 如果不这么做会怎样?

  • 字节码无法被执行,程序卡死;
  • 如果没有 JIT,Spring Boot 处理 HTTP 请求会非常慢(每次都要解释字节码)。

💡 Spring Boot 的启动速度、运行性能,直接依赖执行引擎的效率。


4️⃣ 本地方法接口(JNI, Java Native Interface)

✅ 它是什么?

JVM 提供的调用操作系统本地功能(C/C++ 代码)的桥梁。

✅ 它做了什么?怎么做的?

在 Spring Boot 启动过程中,JNI 被隐式调用多次:

  • 创建线程 :主线程、Tomcat 工作线程 → 调用 OS 的 pthread_create(Linux);
  • 文件读取 :加载 application.properties、读取 JAR 内资源 → 调用 OS 文件 API;
  • 网络绑定 :Tomcat 启动时绑定 8080 端口 → 调用 socket(), bind() 等系统调用;
  • 时间获取 :日志打时间戳 → 调用 gettimeofday()

这些操作在 Java 层通过 native 方法实现,如 FileInputStream.read0()

✅ 为什么要这么做?

  • Java 不能直接操作硬件或操作系统;
  • 但 Spring Boot 需要文件、网络、线程、时间等基础能力;
  • JNI 让 JVM 能安全地"借用"操作系统能力。

❌ 如果不这么做会怎样?

  • 无法读配置文件 → Spring Boot 启动失败;
  • 无法绑定端口 → Web 服务无法启动;
  • 无法创建线程 → Tomcat 无法处理并发请求。

💡 没有 JNI,Java 就是一个"沙盒玩具",Spring Boot 根本无法作为真实应用运行。


5️⃣ 垃圾回收器(Garbage Collector, GC)

✅ 它是什么?

JVM 中自动回收无用对象的模块,防止内存泄漏。

✅ 它做了什么?怎么做的?

Spring Boot 启动时:

  • 创建大量临时对象:类元数据、Bean 定义、配置解析中间对象;
  • GC(如 G1 或 ZGC)在后台运行:
    • 识别哪些对象不再被引用(如解析完的 YAML 节点);
    • 回收它们占用的堆内存;
  • 启动完成后,GC 继续监控运行时对象(如 HTTP 请求产生的临时对象)。

✅ 为什么要这么做?

  • Spring Boot 启动过程会瞬时创建大量对象(可达数十万);
  • 如果不自动回收,堆内存迅速耗尽 → OutOfMemoryError
  • 手动管理内存(如 C++ 的 delete)在复杂框架中几乎不可能。

❌ 如果不这么做会怎样?

  • 程序启动到一半就 OOM 崩溃;
  • 即使启动成功,处理几个请求后内存耗尽;
  • 开发者必须手动跟踪每个对象生命周期------在 Spring 这种高度动态的框架中完全不可行

💡 GC 是 Spring Boot 能"开箱即用"的关键:开发者无需关心内存,专注业务逻辑。


🧭 总结:Spring Boot 启动时 JVM 五大组件协作全景

组件 在 Spring Boot 启动中的角色 关键性
类加载器 加载 MyApplication 和所有 Spring 类 ⭐⭐⭐⭐⭐(没它,连 main 都找不到)
运行时数据区 存类信息、方法栈、Bean 对象 ⭐⭐⭐⭐(数据无处安放)
执行引擎 执行 main() 和 Spring 初始化逻辑 ⭐⭐⭐⭐⭐(没它,代码只是文本)
JNI 提供文件、网络、线程等 OS 能力 ⭐⭐⭐⭐(没它,Spring Boot 是"残废")
GC 自动清理启动过程中的临时对象 ⭐⭐⭐(没它,内存迅速耗尽)
相关推荐
小满、3 小时前
什么是栈?深入理解 JVM 中的栈结构
java·jvm·1024程序员节
Le1Yu3 小时前
ElasticSearch倒排索引、ES核心概念、JAVA集成ES操作
java
方二华4 小时前
5 mysql源码中B+树的构建
数据库·mysql·1024程序员节
侯喵喵4 小时前
Jetson orin agx配置ultralytics 使用docker或conda
yolo·docker·1024程序员节·ultralytics
星火科技探索实验室4 小时前
【实战经验】飞牛云 如何使用 SSD 缓存加速?
1024程序员节
千百元4 小时前
java 程序Apache log4j JDBCAppender SQL注入漏洞(CVE-2022-23305)
1024程序员节
开心-开心急了4 小时前
Kivy 乒乓游戏教程 基于Minconda或Anconda 运行
python·conda·1024程序员节·kivy
西部风情6 小时前
聊聊并发、在线、TPS
android·java·数据库
诗句藏于尽头7 小时前
自动签到之实现掘金模拟签到
python·1024程序员节