什么是 AOT 编译?和 JIT 有什么区别?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/

面试考察点

面试官抛出这个问题,表面上是问两个概念的区别,实际上是想考察你这几个方面的能力:

  1. 对 Java 程序执行过程的理解: 你清不清楚一个 .java 文件从保存到运行,中间经历了哪些步骤?是直接变成机器码,还是需要中间层?
  2. 对 JVM 核心机制(JIT)的掌握: 你知不知道 JIT 编译器在 JVM 里扮演什么角色?它为什么能让 Java 程序变快?它的工作原理“热点代码”你了解吗?
  3. 对新技术趋势(AOT)的敏感度: 你是否关注过 Java 领域的新发展,比如 Spring Native、GraalVM、Quarkus 这些火的不得了的技术?它们背后依赖的 AOT 编译,你了解多少?
  4. 对比分析与场景判断能力: 面试官不仅仅是想听你背出 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 是在构建时静态编译,以 “牺牲部分动态性和峰值性能” 换取 “极速启动和低内存”。

理解了这一点,你就能根据具体的业务场景,在两者之间做出最合适的选择了。