当前位置: 首页 > news >正文

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

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

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


🧩 前提:Spring Boot 程序的本质

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

@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自动清理启动过程中的临时对象⭐⭐⭐(没它,内存迅速耗尽)
http://www.dtcms.com/a/523666.html

相关文章:

  • 个性化网站建设开发李沧建网站公司
  • 益品康丰集团:以科技重塑康养未来,让健康触手可及
  • 华为Watch GT 6:运动与科技的完美融合
  • 微算法科技(NASDAQ MLGO)开发基于区块链的差分优化联邦增量学习算法,提高机器学习的性能与安全性
  • 《水龙吟》开播即热 李家豪化身“阳光侠客”点亮玄侠江湖
  • Linux基础 -- UBI模块之 leb_read_sanity_check函数说明
  • 深入解析 Transformer 模型:以 ChatGPT 为例从词嵌入到输出预测的大语言模型核心工作机制
  • 破局延时任务(上):为什么选择Spring Boot + DelayQueue来自研分布式延时队列组件?
  • 云手机是一种应用软件吗?
  • 工业无线通信突破!SG-Lora-TCP 模块,7 公里无线替代 TCP 布线
  • 网站建设 服务内容 费用上海有几个区最好
  • 现代前端状态管理深度剖析:从单一数据源到分布式状态
  • UART 串口协议详解与 STM32 实战实现
  • 【CMakeLists.txt】QtSvg 头文件包含配置详解
  • 调用Zlib库接口压缩、解压缩(C++源码)
  • flume的log4j日志无输出排查
  • 一个域名可以做两个网站吗天津人事考试网
  • whisper 模型处理音频办法与启示
  • linux rt任务调度器
  • 金融智能体技术解读:十大应用场景与AI Agent架构设计思路
  • 永磁同步电机(PMSM)在MATLAB中的高级调参策略与实践
  • 李宏毅机器学习笔记31
  • 【timecode】两种不同的时间码格式:“`00:00:00`” 和 “`00:00:00:00`”
  • 个人网站 不用备案深圳建设网站和公众号
  • npm 安装 canvas 报错 node-gyp ERR! 的解决方法(Windows 系统)
  • 编辑器汇总:Neovim、Helix、Vim、LazyVim、Kakoune、nb、Lite XL
  • 如何开发一个自己的包并发布到npm
  • 商城型网站的概念企业信息平台登录
  • Docker MySQL 单主从及分表函数
  • UE5 蓝图-11:本汽车蓝图的事件图表,汽车拆分事件,染色事件(绿蓝黄青)。