IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Debug and Release build :👈 | 👉:Terms_Of_Use |
JIT Compilation |
When a managed application (such as a .NET or Java application) runs, the runtime does not compile entire assemblies or classes into native code all at once. Instead, when a type is loaded, the runtime creates a stub for each of its methods.
The first time a specific method is invoked, the stub passes execution control to the JIT compiler, which translates that individual method's Intermediate Language (IL/bytecode) into native machine instructions.
The stub is then overwritten to point directly to the newly compiled native code for all subsequent calls.
The working set represents the amount of physical memory (RAM) currently allocated to a process. Per-method JITting has a profound dual effect on this footprint:
Piecemeal Loading: Because compilation happens lazily, any code paths, helper methods, or entire classes within an assembly that are never called during execution are never compiled into native memory.
Memory Conservation: This drastically cuts down on unnecessary memory usage, preventing massive assemblies from bloating the working set with native instructions for features the user hasn't triggered.
While lazy compilation saves space on dead code, the JIT process itself introduces two unique forms of memory overhead that expand the working set:
The Code Cache: The native machine code generated by the JIT compiler must live in a dedicated, read-execute block of physical memory (often called the Code Cache or JIT Code Heap). This native code is typically larger in byte size than the heavily compressed IL/bytecode it replaced.
JIT Runtime Data Structures: To perform the compilation, the JIT compiler must allocate internal memory structures to analyze the method, build intermediate representations, allocate CPU registers, and maintain metadata. Though some of this metadata is transient, much of it stays in the working set to support runtime execution, reflection, and debugging.
| Aspect | Per-Method JIT (Default) | Ahead-Of-Time / Per-Assembly Pre-JIT (e.g., Native AOT, NGen) |
|---|---|---|
| Compilation Timing | Dynamically at runtime on the first method call. | Compiles everything prior to execution. |
| Startup Working Set | Lower; only initial startup methods are compiled. | Higher; native code for the entire assembly is mapped into memory immediately. |
| Steady-State Working Set | Variable; can be lean if much of the assembly goes unused, but includes JIT compiler memory overhead. | Predictable; higher baseline footprint but avoids runtime JIT infrastructure overhead. |
Primarily per-method.
When a .NET assembly is loaded, the entire assembly is not immediately JIT-compiled. Instead:
Example:
class Program
{
static void Main()
{
Foo(); // JIT compiled on first call
Foo(); // Uses existing native code
// Bar() is never called
}
static void Foo() { }
static void Bar() { }
}
In this example:
Main() is JIT-compiled before execution.Foo() is JIT-compiled when first called.Bar() is never JIT-compiled because it is never executed.This behavior helps keep the application's working set smaller.
If JITting occurred per-assembly:
Assembly loaded → Compile every method → Allocate native code for every method
Even methods that are never used would consume memory.
With per-method JITting:
Assembly loaded → Compile only methods actually executed → Unused methods remain as IL
Benefits:
For large applications containing thousands of methods, only a fraction may ever execute during a particular run.
The downside is that the first call to a method incurs JIT compilation overhead:
First call: JIT compile + execute Later calls: execute only
This can cause a brief delay the first time a code path is hit.
To address startup scenarios, .NET also supports:
which perform compilation ahead of time and reduce runtime JIT work.
JIT compilation in .NET occurs primarily on a per-method basis, not per assembly. When an assembly is loaded, its IL code remains uncompiled until a method is invoked. The CLR JIT-compiles each method the first time it is executed and then caches the generated native code for subsequent calls. This approach reduces startup time and keeps the working set smaller because only methods that are actually used consume memory for native code, while unused methods remain as IL.
| Debug and Release build :👈 | 👉:Terms_Of_Use |