In this lesson, you will learn how to configure heap segments to ensure your .NET applications behave predictably when deployed into constrained containerized environments.
The Challenge of Fixed-Memory Environments
In a classic server environment, the .NET Garbage Collector (GC) dynamically scales its heap size based on total physical memory. However, in containerized environments (like Kubernetes or Docker), the container often has a hard memory limit enforced by the orchestrator. If the GC is unaware of these limits, it may attempt to expand its heap segments beyond the container’s quota, leading to an OutOfMemoryException and subsequent termination by the container runtime—even if the application logic itself is memory-efficient.
Configuring GC Heap Limits
To prevent the GC from over-allocating, you must align the heap segments with your container’s actual available memory. Since .NET Core 3.0+, the GC is "container-aware," but you can manually tune these boundaries using runtime configuration to optimize for throughput or responsiveness.
You configure these settings primarily through the runtimeconfig.json file or environment variables. The two most critical settings for region tuning are DOTNET_GCHeapHardLimit and DOTNET_GCHeapHardLimitPercent.
For example, if you have a container restricted to 2GB of RAM, you can set the following environment variable to prevent the GC from exceeding 80% of that limit:
{
"runtimeOptions": {
"configProperties": {
"System.GC.HeapHardLimit": "1610612736"
}
}
}
By setting a HeapHardLimit, you force the GC to become more aggressive in collecting unreachable objects as it approaches that threshold. This effectively "tunes" the internal regions to work within the envelope defined by your container orchestration. Without this tuning, the GC might wait too long to collect, triggering a hard OOM kill before it ever decides that memory pressure warrants an intensive collection cycle.
When tuning these segments, always ensure your limit accounts for non-GC memory (like JIT-compiled code, thread stacks, and native allocations), typically leaving a buffer of 20-30% of your total container memory limit for overhead.
Key Takeaway: By setting GCHeapHardLimit, you constrain the GC heap segments to match your container's memory quota, preventing premature termination while forcing the GC to manage memory cycles proactively within your restricted environment.