什么是 AOT 编译?和 JIT 有什么区别?
2026年02月28日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官抛出这个问题,表面上是问两个概念的区别,实际上是想考察你这几个方面的能力:
- 对 Java 程序执行过程的理解: 你清不清楚一个
.java文件从保存到运行,中间经历了哪些步骤?是直接变成机器码,还是需要中间层? - 对 JVM 核心机制(JIT)的掌握: 你知不知道 JIT 编译器在 JVM 里扮演什么角色?它为什么能让 Java 程序变快?它的工作原理“热点代码”你了解吗?
- 对新技术趋势(AOT)的敏感度: 你是否关注过 Java 领域的新发展,比如 Spring Native、GraalVM、Quarkus 这些火的不得了的技术?它们背后依赖的 AOT 编译,你了解多少?
- 对比分析与场景判断能力: 面试官不仅仅是想听你背出 JIT 和 AOT 的定义,更想知道 你能否分析出它们各自的优缺点,以及在什么样的业务场景下应该选择谁。这是一个架构师必备的权衡能力。
核心答案
简单直接地说:
- JIT(Just-In-Time,即时编译): 是指在程序运行过程中,将高频执行的 “热点代码” 动态地编译成机器码,以提升执行效率。它是目前主流 JVM 的标配。
- AOT(Ahead-Of-Time,预编译): 是指在程序运行之前,直接将源代码(或中间代码)预先编译成目标机器的机器码。生成的是一个可以直接运行的可执行文件,通常体积较小且启动极快。
深度解析
好,定义大家都懂,但背后的门道才是面试官想听的。咱们一层层往下扒。
原理/机制:它们是怎么工作的?
JIT 编译(以 HotSpot VM 为例)
Java 代码的传统运行流程是这样的:
- 我们写的
.java文件,首先通过javac编译成平台无关的字节码(.class文件)。 - 当程序启动,JVM 会通过解释器逐行解释执行字节码。这个阶段启动很快,但执行速度慢。
- JVM 在运行时会进行监控,统计出哪些方法或代码块被调用的频率特别高,这些代码就被称为 “热点代码”。
- 一旦代码被标记为热点,JVM 的后台编译器——JIT 编译器就会登场。它会把这些热点代码直接从字节码编译成与当前操作系统和 CPU 架构匹配的机器码。
- 编译后的机器码会被缓存起来,下次再执行这段逻辑时,就直接运行机器码,速度飞快。
所以你看,JIT 是一个“边跑边优化”的过程,它用启动时间和额外的内存开销(用于存储编译后的机器码和监控信息),换取了程序运行时的长期高性能。
AOT 编译(以 GraalVM Native Image 为例)
AOT 的思路完全不同,它信奉的是“不打无准备之仗”。
- 在构建阶段(比如用 Maven 或 Gradle 打包时),AOT 编译器(如 GraalVM 的
native-image工具)会分析你的代码,包括所有依赖库。 - 它会尝试在编译期就确定所有类的信息、方法的调用关系,并直接将这些 Java 代码(字节码)编译成可执行的机器码。
- 最终生成的是一个脱离了 JVM 的、可以直接运行的本地可执行文件(比如 Linux 下的
-linux-amd64可执行文件)。
这个过程中,AOT 编译器会进行大量的静态分析和优化,但受限于 “在运行前无法知道所有动态行为”,它没法做 JIT 那种基于运行时 profiling 的激进优化。
对比分析:JIT vs AOT,一张表看清楚
为了更直观,我整理了一个对比表格,这也是面试时展示你逻辑清晰的好方式。
| 特性维度 | JIT (即时编译) | AOT (预编译) |
|---|---|---|
| 编译时机 | 运行时(程序执行过程中) | 构建时(程序运行之前) |
| 核心目标 | 追求长期运行时的峰值性能 | 追求快速启动和低内存占用 |
| 启动速度 | 慢。需要等待 JVM 启动、类加载、解释执行,直到 JIT 介入。 | 极快。直接运行机器码,无需等待 JVM 初始化。 |
| 峰值性能 | 极高。能根据运行时 profiling 进行深度优化,如方法内联、分支预测等。 | 相对较低。无法利用运行时信息进行激进优化,优化程度有限。 |
| 内存占用 | 较高。JVM 本身、元空间、以及 JIT 编译后的机器码缓存都需要内存。 | 较低。没有 JVM 运行时开销,生成的二进制文件紧凑。 |
| 跨平台性 | 强。一次编译,到处运行(只要有对应平台的 JVM)。 | 弱。编译时即绑定特定操作系统和 CPU 架构。换平台需要重新编译。 |
| 动态特性 | 完全支持。完美支持反射、动态代理、Instrumentation 等。 | 受限支持。反射等动态特性在编译期难以分析,需要额外配置或存在限制。 |
| 典型应用 | 传统的 Spring Boot 应用,长时间运行的后端服务。 | Serverless 函数、微服务、对启动时间敏感的命令行工具。 |
3. 最佳实践与场景选择
知道了区别,我们该怎么选?这才是问题的价值所在。
-
什么时候首选 JIT?
- 你的应用是长时间运行的后端服务,比如一个复杂的电商系统、金融核心系统。启动慢个几十秒可以接受,但运行时的 TPS(每秒事务数)和响应延迟是命根子。
- 你的代码重度依赖反射、动态代理、CGLIB 等动态特性。比如 Spring 框架本身就大量使用这些,在 JIT 环境下运行得最好。
-
什么时候可以拥抱 AOT?
- 你正在开发 Serverless 应用(FaaS)。这种场景下,函数实例经常是 “用完即毁”,每次启动都要快,冷启动时间直接决定了用户体验和成本。AOT 编译的应用可以在毫秒级启动,完美匹配。
- 你需要构建轻量级的容器镜像,比如使用
distroless甚至scratch镜像,把应用和依赖打包成一个极小的二进制文件,方便部署和分发。 - 你在开发一些命令行工具,希望用户下载后能瞬间响应,不用等 JVM 慢慢 “热身”。
常见误区
- 误区一:AOT 比 JIT 快。
- 真相: 只有在“启动速度”上,AOT 绝对领先。在 “长期运行的峰值性能” 上,经过充分热身的 JIT 优化后的代码,通常要比 AOT 生成的代码快得多。
- 误区二:有了 AOT,JIT 就要被淘汰了。
- 真相: 两者是互补关系,而不是替代关系。Java 生态非常庞大,Oracle 和社区也在同时发展两者。JIT 依然是通用后端服务的王者,而 AOT 则开辟了新的战场(云原生、Serverless)。Java 21 的虚拟线程配合 AOT,更是未来可期。
- 误区三:用 AOT 编译我的 Spring Boot 应用,直接就能跑。
- 真相: 没那么简单。Spring 的很多自动化配置、条件注解都依赖于运行时动态判断。Spring Native(现已被 Spring Boot 3 的 AOT 引擎集成)项目做了大量工作,但依然需要对代码进行一些调整,比如需要提前注册反射使用的类,否则会报错。
总结
JIT 是在运行时动态优化,以 “预热” 换取 “高峰性能”;AOT 是在构建时静态编译,以 “牺牲部分动态性和峰值性能” 换取 “极速启动和低内存”。
理解了这一点,你就能根据具体的业务场景,在两者之间做出最合适的选择了。