JIT编译器何时将应用程序方法、代码块从CIL转换为原生机器码?
Great question—let’s break down the key scenarios where the JIT (Just-In-Time) compiler kicks in to convert Common Intermediate Language (CIL) to native machine code. I’ve debugged countless performance issues tied to JIT behavior, so this is a topic I’m familiar with.
Primary Trigger: First-Time Execution of a Method/Code Block
This is the most common and core scenario. When the CLR loads your assembly, it doesn’t immediately compile all CIL to native code—this is called lazy compilation. Instead:
- The first time a method is called (e.g.,
MyService.ProcessData()), the CLR checks if there’s already native code cached for that method. - If no cached native code exists, the JIT compiler is invoked to translate the method’s CIL into optimized native machine code for the target CPU architecture.
- Once compiled, the native code is stored in memory. All subsequent calls to the same method will reuse this cached code, avoiding re-compilation overhead.
Tiered Compilation: Background Optimization for Hot Methods
Modern .NET runtimes (Core, 5+) enable tiered compilation by default, which splits JIT compilation into two phases:
- Tier 0: When a method is first called, the JIT quickly generates a basic, unoptimized version of native code. This prioritizes fast startup, so your application becomes responsive sooner.
- Tier 1: As your application runs, the runtime tracks which methods are called frequently (hot methods). A background JIT thread then re-compiles these hot methods with full optimizations (e.g., inlining, loop unrolling). Once the optimized Tier 1 code is ready, subsequent calls to the method switch to using this improved version.
Explicitly Triggered JIT Compilation
You can force the JIT to compile a method ahead of its first execution using .NET APIs, like:
System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(typeof(MyClass).GetMethod(nameof(MyClass.MyMethod)).MethodHandle);
This is often used during application warm-up phases to eliminate JIT overhead for critical methods before they’re used in production traffic.
Generic Method Instance First Execution
Generic methods are compiled per specific type parameter combination. For example:
- The first time you call
List<int>.Add(42), the JIT compiles theAddmethod specifically forint. - The first time you call
List<string>.Add("hello"), the JIT compiles a separate native version ofAddforstring.
This is because value types (likeint) require different memory handling than reference types, so each generic instance needs its own native code.
Edge Case: Pre-Compiled Assemblies (Non-JIT Scenarios)
Note that if your assembly is pre-compiled using tools like ReadyToRun (R2R) or legacy NGen, most CIL is converted to native code at build time. In these cases, the JIT only kicks in for code that wasn’t pre-compiled (e.g., dynamically generated CIL, some generic instances).
内容的提问来源于stack exchange,提问作者Sybs77

