摘要最近Spring 生态的一系列动作被外界解读为「Spring 官宣换掉 JVM」。这个说法足够劲爆却也不够准确。本文从 Spring、GraalVM、Project Leyden、CRaC 等关键技术讲起系统梳理 JVM 的瓶颈、原生镜像的原理、Spring Boot 3 的 AOT 引擎以及两者的真实性能差异并给出「什么时候该用原生镜像、什么时候继续拥抱 JVM」的工程判断帮助你在喧嚣中看清 Java 未来的真实走向。1. 风暴眼一则让 Java 圈炸锅的「官宣」如果评选近几年 Java 世界最具传播力的标题「Spring 换掉 JVM」绝对名列前茅。它把两个重量级关键词绑在一起一个是企业级开发事实标准 Spring另一个是陪伴 Java 二十多年的 Java 虚拟机。于是很多开发者第一反应是惊讶Spring 是不是彻底放弃了 JVM我手中的技术栈是不是又要重学这种不确定性恰恰是标题党最容易发酵的土壤。但在深入技术细节之前我们必须先把事实摆正。Spring 官方从来没有说过「我们要淘汰 JVM」「请开发者不要再使用 JVM」。真实情况要微妙得多Spring 正在大力拥抱 GraalVM Native Image在 Spring Boot 3 中把 AOT 编译提升为一等公民同时积极跟进 OpenJDK 的 Project Leyden 和 CRaC 项目。与其说「换掉 JVM」不如说 Spring 正在为 Java 应用提供一种新的运行形态目标是解决 JVM 在云原生时代暴露出的启动速度慢、内存占用高、冷启动不可预测等短板。换句话说JVM 依然是 Java 世界的根基Spring 依然基于 JVM 进行开发和测试。真正发生变化的是部署与运行层面的选择权未来你可以把同一个 Spring 应用构建成传统 JVM 应用也可以构建成秒级启动、低内存占用的原生可执行文件。这种「双形态」才是 Spring 官方战略的准确概括。那么这场风暴到底由哪些事实引爆让我们按时间线快速梳理几个关键节点。第一Oracle 持续推进 GraalVM 项目并将其定位为「为现代工作负载打造的高性能 JDK」第二Spring 团队投入大量资源开发 Spring Native最终在 Spring Boot 3 中将原生支持整合进核心发布第三OpenJDK 内部启动 Leyden 项目直接对 JVM 的启动时间和内存占用动刀第四Azul、Red Hat 等厂商推动 CRaC 这样的检查点恢复技术。这一连串事件叠加才让外界产生了「JVM 即将被替换」的错觉。本篇文章将沿着技术演进的主线把每一个概念讲透把每一种选择的边界讲清。读完你会发现这场变革并不是「JVM 的葬礼」而是 Java 生态一次迟到的自我进化。2. 先泼一盆冷水Spring 没有也不会「换掉 JVM」先把结论说清楚任何宣称「Spring 已官宣放弃 JVM」的说法都是错误解读。Spring 官方的态度一直非常明确——Spring Framework 和 Spring Boot 本身就是用 Java 编写的它们运行在 JVM 之上这一点在可预见的未来不会改变。原生镜像不是「替换 JVM」而是「换一种打包与启动方式」底层依然离不开 Java 生态。我们需要理解一个关键区别编译时和运行时。GraalVM Native Image 所做的工作是在构建阶段把字节码提前编译成特定操作系统和架构下的机器码并打包成一个独立可执行文件。这个过程并没有抹掉 Java 语言本身反而是在另一种执行模型下运行你的 Java 代码。你的业务逻辑、框架能力甚至你熟悉的异常体系、对象模型在很大程度上仍然保留着 Java 的语义。那么为什么官方动作会被解读为「放弃 JVM」原因主要有三个。其一营销与流量原生镜像带来的启动性能提升太夸张厂商和博主都倾向于用震撼标题表达这种变化其二概念混淆很多人把「JVM 运行模式」和「Java 语言」混为一谈以为不用 JVM 就等于不用 Java其三焦虑驱动云原生时代对弹性伸缩的要求越来越苛刻很多团队确实希望摆脱 JVM 的冷启动痛苦于是「换掉 JVM」成了情绪出口。从 Spring 官方文档看Spring Boot 3 的定位是「同时支持 JVM 和原生镜像两种运行方式」。官方甚至反复强调原生镜像并不适合所有场景传统 JVM 模式在吞吐量、动态性和生态兼容性方面仍然具有不可替代的优势。所以与其说这是二选一的革命不如说这是形态的丰富。如果要用一句话回应标题党那就是Spring 没有换掉 JVM它只是给了开发者一个不再被 JVM 启动速度绑架的新选项。接下来的内容我们会把这个选项的原理、代价和收益彻底展开。3. JVM 的辉煌与软肋要理解这场变革必须先理解 JVM 为什么曾经如此成功又为什么在云原生时代显得力不从心。JVM 的核心魅力在于「一次编写到处运行」。Java 代码编译成平台无关的字节码JVM 负责把字节码翻译成具体平台的机器指令并通过 JIT 即时编译器在运行时不断优化热点代码。JIT 的威力在长时间运行的服务中表现得淋漓尽致。应用启动后JVM 会先以解释方式执行代码同时收集方法调用次数、分支概率、类型信息等运行数据。当某个方法被频繁调用时JIT 会把它编译成本地机器码并根据观察到的情况做激进的内联、逃逸分析、锁消除等优化。理论上一个充分「预热」的 JVM 应用其吞吐量可以媲美甚至超过 AOT 编译的原生应用因为 AOT 缺乏运行时信息无法做这样的自适应优化。然而这套设计在云计算大规模普及后暴露出明显的短板。首先是启动速度。一个典型的 Spring Boot 服务在传统 JVM 模式下启动需要几秒到几十秒因为这期间不仅要初始化 JVM还要加载类、扫描注解、构建 Bean 容器、建立连接池。其次是内存占用。JVM 需要加载大量元数据、类信息、JIT 编译产物和运行时结构这使得基础内存占用往往达到数百 MB在函数计算、边缘计算等资源敏感场景中显得昂贵。第三是预热过程。JIT 需要时间才能达到最佳性能而云环境中的实例可能刚启动就被替换根本来不及「热身」。这些软肋并非 JVM 的「设计错误」而是历史包袱与通用性设计的必然结果。JVM 诞生于服务器长期驻留的年代它默认应用会运行数小时甚至数天因此愿意用启动时间换取稳态性能。但在 Serverless、微服务弹性伸缩、容器化快速扩缩容的新世界里几秒钟的延迟和数百 MB 的内存都可能成为致命伤。于是整个 Java 生态开始寻找答案。大体上有三条路线其一优化 JVM 本身例如加快类加载、改进 CDS 归档、减少初始化开销其二采用 AOT 提前编译例如 GraalVM Native Image把启动成本尽量前移到构建期其三采用检查点与恢复技术例如 CRaC把已经启动好的进程状态保存下来需要时直接恢复。这三条路线的交汇构成了我们今天看到的「JVM 换装」全景。4. GraalVM从实验室走出来的新引擎在三条路线中GraalVM Native Image 是声势最大、也最被外界误解为「替代 JVM」的技术。GraalVM 最初由 Oracle 实验室发起它本身并不是要取代 JVM而是一个多语言运行时平台可以运行 Java、JavaScript、Python、Ruby、R 等语言。但让它真正出圈的是 Native Image 功能。GraalVM Native Image 采用了一种新的编译策略在构建阶段它会对 Java 字节码执行复杂的静态分析找出应用实际会用到哪些类、字段、方法然后关闭世界假设把所有可能被访问到的代码连同必要的运行时一起提前编译成单个可执行文件。这个文件不依赖 JVM 即可运行启动时间可以压缩到毫秒级内存占用也大幅下降。这里有一个容易被忽略的关键词关闭世界假设。JVM 之所以动态性强是因为它可以在运行时加载任意类、通过反射实例化任意对象、动态生成代理。这种开放世界的灵活性恰恰是 AOT 编译最难处理的部分。GraalVM 的做法是在构建期通过静态分析尽可能穷举可达代码并把这些信息固化到镜像中。如果代码中存在不可预测的动态行为开发者需要通过配置文件告诉编译器哪些类、方法需要保留。GraalVM Native Image 的收益非常直观。一个原本需要数秒启动、占用数百 MB 内存的微服务编译成原生镜像后可能只需要几十毫秒启动、占几十 MB 内存。这种量级的提升在函数计算、冷启动敏感的服务、需要快速扩容的高并发场景中具有巨大的吸引力。正因如此Spring 团队才投入大量精力让 Spring Boot 应用能够以较低成本享受到原生镜像的红利。但收益永远有代价。原生镜像的构建时间远长于普通 JVM 打包通常需要几分钟它对反射、动态代理、序列化、资源加载等动态特性有严格限制它的峰值吞吐量在长时间运行后往往略逊于充分预热的 JVM而且不同平台之间不能直接复用同一份镜像。理解这些代价才能做出理性的技术选型。5. Spring Native 的艰难探索Spring 与 GraalVM 的结合并非一蹴而就。早在 GraalVM Native Image 还处于实验阶段时Spring 团队就启动了 Spring Native 项目目标是让 Spring Boot 应用可以被编译成原生镜像。起初这条路走得相当艰难。难点主要来自 Spring 框架本身的高度动态特性。Spring 依赖反射进行依赖注入和 Bean 创建依赖 CGLIB 或 JDK 动态代理生成切面和事务代理依赖各种策略接口和条件配置在运行时决定具体实现。这些特性在传统 JVM 上灵活而强大但在 AOT 静态分析面前却像一片迷雾编译器很难自动判断哪些类需要被保留、哪些方法需要被反射调用。早期 Spring Native 的解决方案是大量提供「原生提示」配置。开发者需要手工编写反射配置、代理配置、资源配置文件告诉 GraalVM 编译器哪些内容不能被裁剪。对于简单的 Demo这已经足够但对于真实的企业级应用动态特性无处不在手工维护这些配置几乎是不可能完成的任务稍有不慎就会出现运行时缺失类、反序列化失败等诡异问题。尽管存在诸多不便Spring Native 的实验价值不可低估。它验证了「Spring 应用可以被原生编译」这一命题的可行性也为后续的架构演进指明了方向与其让开发者手工维护提示配置不如让框架在构建期就参与代码生成把运行时的工作前置到编译时。这个思路最后催生了 Spring Boot 3 中真正成熟的 AOT 引擎。6. Spring Boot 3 与 AOT 引擎Spring Boot 3 是这场变革的分水岭。它不再把原生支持当作一个附加的实验模块而是将 AOT 处理整合进核心构建流程。所谓 AOT即 Ahead-of-Time提前编译或提前处理。在 Spring 的语境中AOT 有两个层面一是 Spring 自身的 AOT 处理在构建期分析代码并生成提示与优化代码二是 GraalVM Native Image 的 AOT 编译把处理结果编译成机器码。Spring Boot 3 的 AOT 引擎做了几件重要的事。首先它在构建阶段对应用进行分析识别出 Bean 定义、自动配置、条件装配等信息并生成相应的原生提示文件。其次它会把许多原本在运行时才完成的 Bean 创建逻辑提前生成代码例如生成 Bean 的实例化方法、依赖注入代码从而减少运行时的反射调用。第三它能够生成 GraalVM 所需的 reflect-config、resource-config、proxy-config 等配置让开发者从繁琐的手工提示中解放出来。对于大多数符合「常规」写法的 Spring Boot 应用这套机制已经能够做到开箱即用。你几乎不需要额外配置就能把应用编译成原生镜像。只有当应用使用了大量非常规动态特性时才需要补充少量提示。这相比 Spring Native 时代是巨大的进步。还需要说明的一点是AOT 处理同样可以服务于传统 JVM 模式。即使你最终仍然运行在 JVM 上开启 AOT 分析也能提前生成部分代码和配置从而在一定程度上缩短启动时间。只是这种模式下的优化幅度远不如原生镜像那么夸张。因此AOT 在 Spring Boot 3 中既是原生镜像的基石也是 JVM 启动优化的一种手段。7. 原生镜像的运行机制要理解原生镜像与 JVM 模式的区别不妨从运行时的角度拆解一下。传统 JVM 应用的执行链条是启动 JVM 进程初始化类加载器和运行时加载并验证类解释执行字节码JIT 编译热点代码垃圾回收管理对象。这条链条高度动态但也意味着大量的启动开销和元数据占用。原生镜像则把这条链条大幅前移和简化。构建阶段GraalVM 会做大规模的静态分析确定可达代码集合并把其中的方法编译为机器码。启动时不再有类加载、字节码验证、解释执行等阶段也不需要 JIT 编译器驻留内存。原生镜像使用一种轻量级的 Substrate VM 运行时来管理内存、线程、信号等底层事务对象分配和垃圾回收也经过针对性优化。这种转变带来几个直接结果。第一启动快应用启动只需要加载机器码执行初始化必要的运行时几十毫秒到几百毫秒就能对外服务。第二内存小没有 JIT 编译产物、没有完整类元数据基础内存占用显著降低。第三镜像自包含可执行文件里打包了所需运行时部署时不需要在目标机器上安装 JDK。第四不可在运行时动态加载类因为你已经「关闭了世界」运行时无法像 JVM 那样随意加载新类。最后一个结果既是特性也是约束。它意味着原生镜像天生适合那些在构建期就可以确定行为的应用而不适合依赖大量运行时代码生成、动态脚本、插件加载的应用。理解了这条边界你就能很快判断一个项目是否适合原生化。8. Project LeydenJVM 的自我革命如果说 GraalVM Native Image 是「外部给 Java 提供另一种运行形态」那么 OpenJDK 的 Project Leyden 就是 JVM 内部的自我革命不放弃 JVM但把 JVM 的启动和内存问题直接解决掉。Project Leyden 的核心理念可以用一个词概括Java 的静态镜像。它的目标是在不改变 Java 语言和 JVM 生态的前提下通过构建期的预计算和归档技术把应用启动所需的类加载、链接、初始化等工作提前完成并固化到一个「静态镜像」中。运行时直接加载这个镜像跳过大量重复工作从而显著缩短启动时间。Leyden 并不是一个孤立的项目它与现有的 CDS、AOT、AppCDS 等技术一脉相承。它的愿景是把这些分散的优化统一到一个清晰的框架下让普通 Java 应用无需改造、无需 GraalVM也能获得接近原生镜像的启动体验。根据 OpenJDK 公开的方向Leyden 会分阶段推进先做好启动与时序方面的优化再逐步扩展到内存与元数据压缩、更激进的 AOT 编译等领域。需要强调的是Project Leyden 目前仍处于演进过程中离大规模生产落地还有距离。但它传递出的信号极其重要JVM 社区已经承认了启动时间和内存占用是需要优先解决的问题而且打算在 JVM 框架内解决。这意味着未来开发者的选择会更加丰富可以继续用 JVM 并享受 Leyden 带来的加速也可以在极端敏感场景使用 GraalVM 原生镜像。9. Project CRaC检查点与重启除了 AOT 编译之外还有另一条被广泛关注的技术路线检查点与恢复也就是 CRaC。CRaC 的灵感来自容器和虚拟机的快照机制让一个已经完成初始化、处于可服务状态的 JVM 进程「冻结」把内存状态保存为检查点文件下次启动时直接从检查点恢复跳过漫长的初始化过程。CRaC 的最大优势在于它几乎不改变应用的编写方式。你不需要像原生镜像那样规避动态特性也不需要等待漫长的 AOT 构建。应用照常在 JVM 上运行完成启动后拍摄快照即可。恢复时进程会回到拍摄快照那一刻的状态启动时间可以压缩到毫秒级。对于数据库连接、网络套接字这类在冻结时需要特殊处理的外部资源CRaC 提供了相应的钩子机制让应用可以在冻结前和恢复后执行清理与重建。CRaC 的思路与 Azul 的 ReadyNow、红帽推动的相关工作有交叉也容易让人联想到函数计算中的快照复用。它特别适合启动逻辑复杂但运行环境相对稳定的场景。但与原生镜像相比CRaC 恢复后的进程仍然是完整的 JVM 进程内存占用不会像原生镜像那样得到根本性压缩同时检查点文件与具体环境强相关跨机器、跨版本恢复时需要格外小心。综合来看AOT 原生镜像和检查点恢复并不是互相排斥的路线。前者把成本放在构建期追求更彻底的资源瘦身后者把成本放在快照管理上追求对现有应用的平滑加速。两者共同构成了 JVM 之外广阔的性能优化空间也让 Java 在云原生时代重新找回了主动权。10. 工程判断什么时候拥抱原生镜像什么时候继续拥抱 JVM把技术原理讲清楚之后最终的落点还是工程选择。这里没有银弹也没有「原生镜像一定更好」或「JVM 永远不可替代」的标准答案。正确的问题不是「我该不该用原生镜像」而是「在我的业务特征、团队能力和运维约束下哪条路线收益最大、代价最小」。先看原生镜像最适合的场景。首先是函数计算和 Serverless 平台这类环境按调用计费、实例随来随走毫秒级冷启动和低内存占用几乎是硬性要求。其次是需要高频扩缩容的微服务当流量波动剧烈时快速拉起低开销实例比长时间预热更符合弹性诉求。再次是边缘计算、IoT 网关等资源受限环境几十 MB 的基础内存差别可能决定设备能否承载。最后是启动速度直接决定用户体验的 CLI 工具、批处理任务或一次性数据修复程序。再看传统 JVM 更占优的场景。如果应用要长时间稳定运行吞吐量比冷启动更重要那么充分预热的 JIT 仍然能交出很漂亮的成绩单。如果应用重度依赖反射、动态代理、运行时代码生成、插件体系或第三方框架的黑魔法特性原生化改造的成本和风险会显著上升。如果团队需要成熟的在线诊断工具、JMX 监控、热替换或不停机调优能力完整 JVM 提供的动态观测性也是原生镜像目前难以完全替代的。此外大量存量系统在不作大规模重构的前提下继续跑 JVM往往也是最理性的选择。检查点与恢复技术则处于两者之间。它适合那些「启动逻辑复杂、但运行环境相对可控」的场景应用本身不需要改动只需要在某个稳定环境里完成启动并拍摄快照。相比原生镜像它回避了 AOT 静态分析的改造工作但相比普通 JVM它又能显著压缩冷启动时间。代价是快照文件与具体环境强绑定跨机器、跨版本恢复需要额外治理成本。下面给出一张更实用的判断清单优先考虑原生镜像冷启动是主要瓶颈流量弹性大内存预算紧张应用动态特性较少团队能接受更长的构建时间和平台绑定。优先继续用传统 JVM指标以稳态吞吐为主大量使用反射、代理、插件和动态特性需要对运行状态做深度观测存量系统改造风险高。优先考虑检查点恢复不想改造应用但需要毫秒级冷启动运行环境能保持相对稳定愿意为快照管理付出一定治理成本。最后回到这场风波的起点。Spring 并没有官宣换掉 JVM它做的是把 AOT 编译变成一种正式且可选的能力把选择权交到开发者手里。与此同时OpenJDK 的 Project Leyden 在 JVM 内部继续自我革命CRaC 则用快照思路另辟蹊径。GraalVM 原生镜像、传统 JVM 优化和检查点恢复这三条路线并不需要决出胜负它们分别回答了不同约束条件下的同一道题如何让 Java 应用在云原生时代跑得更快、更轻、更省。理解各自的边界才不会被标题党带偏也才能在做技术选型时真正胸有成竹。这不是 JVM 的葬礼而是 Java 生态一次迟到的自我进化。对普通开发者而言最务实的态度不是急着站队而是保持关注、小步试验并在真实的业务场景中验证收益。