JIT编译器在方法首次调用时将IL编译为本地机器码并执行优化,带来首次延迟但支持运行时动态性;AOT则在构建时预编译为原生二进制,启动快、内存低,但丧失反射和动态代码生成能力。

JIT 编译器在运行时做了什么
CLR 的 JIT(Just-In-Time)编译器把 IL(中间语言)在首次调用方法时编译成本地机器码,同时做内联、循环优化、寄存器分配等。这意味着同一段 IL 在不同 CPU 架构或 .NET 运行时版本下可能生成不同质量的汇编码。
典型影响包括:
- 首次执行有明显延迟(如
Console.WriteLine第一次调用比后续慢数倍) - 热路径(hot path)会被多次重编译(tiered compilation),从 Tier 0 到 Tier 1,提升指令级优化强度
- 调试模式下默认禁用部分优化(如
DebuggableAttribute影响内联),导致性能差异可达 2–5×
AOT 编译(如 NativeAOT)如何改变执行模型
NativeAOT 在构建时就将整个程序(含依赖)提前编译为原生二进制,不依赖运行时 JIT,也不加载 coreclr.dll 或 libcoreclr.so。它本质上是“静态链接 + 预优化”的产物。
关键行为差异:
- 启动极快(无 JIT 开销,无元数据解析),适合 CLI 工具或 serverless 场景
- 内存占用更低(无 JIT 编译缓存、无运行时元数据表冗余)
- 但失去运行时类型反射能力(
Type.GetType("Foo")失败)、动态代码生成(Expression.Compile()报错)、以及大部分Assembly.Load*行为 - 泛型实例化必须在编译期确定(
List和List都得显式包含,否则运行时报MissingMethodException)
性能对比:什么时候 JIT 更快,什么时候 AOT 更稳
不是“谁更快”,而是“谁更适合当前负载”。JIT 的优势集中在动态性高的场景;AOT 的优势集中在冷启动敏感、资源受限、且逻辑稳定的场景。
实测常见情况:
- Web API(Kestrel + JSON 序列化):JIT 在持续请求下吞吐略高(约 5–10%),因 tiered JIT 能针对真实请求分布优化热点方法;AOT 启动后首秒响应更稳,但长期吞吐略低(缺少运行时反馈驱动的再优化)
- 命令行工具(如
dotnet-ef替代品):AOT 启动时间从 ~300ms 降到 ~20ms,总执行时间反超 JIT 版本(尤其子命令少、生命周期短) - 带大量反射/插件加载的系统:AOT 直接不可用,除非用
TrimmerRootAssembly+DynamicDependency显式标注,否则会在运行时报System.MissingMetadataException
如何判断你的项目该用哪个
看三个信号:
- 是否调用
Assembly.LoadFrom、Type.GetMethod(含框架自动反射,如 ASP.NET Core MVC 的 action 绑定)—— 是 → JIT 是唯一可行选项 - 是否部署在容器中且要求
OCI image启动 - 是否使用
Span/Memory+Unsafe等底层操作 → AOT 对这些支持良好,但需确认所有DllImport的 native 库已静态链接或随包分发
一个简单验证方式:
dotnet publish -r linux-x64 -p:PublishAot=true如果构建失败,错误里出现
ILLink 相关提示(如 Unresolved assembly 或 RequiresDynamicCode),说明当前代码路径与 AOT 不兼容,得先重构或加 [UnconditionalSuppressMessage] 注解。
真正难的不是选 JIT 还是 AOT,而是识别出那些隐式依赖运行时动态能力的第三方库——比如某些 ORM 的 lazy loading、日志框架的 caller info 提取,它们在 AOT 下会静默失效或抛出异常。










