DOTNET_gcServer and GC Settings
Choose Server or Workstation GC, cap the heap in containers, and set GC knobs through environment variables, runtimeconfig.json or the project file.
DOTNET_gcServer=1 | <ServerGarbageCollection>true</ServerGarbageCollection> | "System.GC.Server": true- Added in .NET .NET Core 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$DOTNET_gcServeroptional | 0 or 1= 1 for Microsoft.NET.Sdk.Web projects, 0 otherwise | Server (1) or Workstation (0). Equivalent to System.GC.Server in runtimeconfig.json and ServerGarbageCollection in the project file. |
$DOTNET_gcConcurrentoptional | 0 or 1= 1 | Background (concurrent) gen2 collections. System.GC.Concurrent / ConcurrentGarbageCollection. |
$DOTNET_GCHeapHardLimitoptional | hex bytes= 75% of the container memory limit when one is set | Maximum GC heap size, for example 0x20000000 for 512 MB. System.GC.HeapHardLimit. |
$DOTNET_GCHeapHardLimitPercentoptional | hex percent= 0x4B (75) inside a memory-limited container | Heap limit as a percentage of available memory, for example 0x46 for 70%. System.GC.HeapHardLimitPercent. |
$DOTNET_GCHeapCountoptional | hex count= logical CPU count | Number of Server GC heaps; caps heaps (and GC threads) below the CPU count. |
$DOTNET_GCDynamicAdaptationModeoptional | 0 or 1= 1 with Server GC in current .NET | DATAS: lets Server GC grow and shrink the number of heaps with load. System.GC.DynamicAdaptationMode. |
Return value
stringSelects the garbage collector flavour when the runtime starts. Server GC uses one heap and one GC thread per logical CPU the process may use; Workstation GC uses one heap.
Errors
Nothing. Unknown or malformed values are ignored. A heap limit too small for the workload ends in OutOfMemoryException rather than the container being OOM-killed.
The GC mode is fixed when the runtime starts, before any of your code runs, so it is set from outside: an environment variable, runtimeconfig.json (generated from the project file), or the project property. Environment variables override runtimeconfig.json. The DOTNET_ prefix is current; the older COMPlus_ prefix still works. Numeric values in these variables are hexadecimal: DOTNET_GCHeapHardLimit=200000000 means 8 GB, not 200 MB.
Server or Workstation
| Server GC | Workstation GC | |
|---|---|---|
| Default for | ASP.NET Core (Web SDK) projects | Console apps, workers on Microsoft.NET.Sdk |
| Heaps and GC threads | One per logical CPU (honours container CPU limits) | One |
| Throughput | Higher, collections run in parallel | Lower |
| Memory | Larger gen0 budgets per heap; higher baseline | Small footprint |
| Best for | Busy APIs with 2+ cores | Many small pods with under 1 CPU, sidecars, CLI tools |
On a machine or container that exposes a single CPU, the runtime uses Workstation GC even when Server GC is requested. In current .NET, Server GC also enables DATAS (dynamic adaptation to application sizes) by default, which starts with fewer heaps and adds them under load; that removed most of the "server GC uses too much memory in small pods" complaints. Set DOTNET_GCDynamicAdaptationMode=0 only if benchmarks show a throughput loss.
Containers
The runtime reads the cgroup limits. With a memory limit, the GC heap is capped at 75% of it by default, leaving room for the runtime, native allocations and thread stacks. Without a memory limit, the heap can grow to the node's memory and the kernel OOM-kills the pod instead of the app getting a catchable OutOfMemoryException. Always set a memory limit; lower GCHeapHardLimitPercent when the process holds significant native memory (gRPC buffers, image processing). Environment.ProcessorCount follows the CPU limit, rounded up, which sets the Server GC heap count and thread pool minimums.
The production failure seen most: a Workstation-GC worker copied into an API (or a ServerGarbageCollection=false left over from a memory scare) that then spends a large share of CPU in gen0 collections under load. Check the actual mode with dotnet-counters or GCSettings.IsServerGC before tuning anything else.
Example
When to use it
- Cap the managed heap of an invoice PDF renderer at 60% of its 1 GiB pod limit so native buffers fit and the pod is not OOM-killed
- Switch a 0.25 CPU sidecar to Workstation GC with DOTNET_gcServer=0 to cut its idle memory
- Find why an orders API burns CPU in GC under load: a copied ServerGarbageCollection=false left it on Workstation GC
Frequently asked questions
Is Server GC on by default in ASP.NET Core?
Yes. The Web SDK sets ServerGarbageCollection to true. Worker services built on Microsoft.NET.Sdk default to Workstation GC unless you set it.
Why does DOTNET_GCHeapHardLimit=500000000 not give me 500 MB?
GC environment variables are parsed as hexadecimal, so that value is about 20 GB. Write 0x1DCD6500 for 500,000,000 bytes, or use the decimal System.GC.HeapHardLimit in runtimeconfig.json.
Does Server GC use more memory in Kubernetes?
It can, because each heap has its own budget. DATAS, enabled by default with Server GC in current .NET, scales heaps with load, and a memory limit caps the heap at 75% of it. For tiny pods Workstation GC is still the leaner choice.
More examples
Project file settings
Project properties are written into <code>bin/.../Orders.Api.runtimeconfig.json</code> at build time and travel with the app. In runtimeconfig.json values are decimal; only the environment variable form is hexadecimal. An environment variable on the container overrides what the file says.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Web SDK default is already true; stated so nobody flips it by accident -->
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
<ItemGroup>
<!-- Becomes System.GC.HeapHardLimitPercent in runtimeconfig.json (decimal here) -->
<RuntimeHostConfigurationOption Include="System.GC.HeapHardLimitPercent" Value="70" />
</ItemGroup>
</Project>Container limits and GC variables together
The CPU limit of 2 gives <code>Environment.ProcessorCount</code> 2, so Server GC uses up to two heaps. <code>0x3C</code> is 60 in hex, leaving 40% of the 1 GiB limit for native buffers so the kernel does not kill the pod before the GC reacts.
apiVersion: apps/v1
kind: Deployment
metadata:
name: invoice-renderer
spec:
replicas: 4
selector:
matchLabels:
app: invoice-renderer
template:
metadata:
labels:
app: invoice-renderer
spec:
containers:
- name: worker
image: registry.example.com/invoice-renderer:2.3.0
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
env:
# 60% of 1Gi for the managed heap; PDF rendering uses native memory too
- name: DOTNET_GCHeapHardLimitPercent
value: "0x3C"
- name: DOTNET_gcServer
value: "1"
runtimeconfig.template.json
A <code>runtimeconfig.template.json</code> next to the project file is merged into the generated runtimeconfig.json. It suits settings without a dedicated MSBuild property. Values are decimal here, and environment variables set on the host still take precedence.
{
"configProperties": {
"System.GC.Server": true,
"System.GC.Concurrent": true,
"System.GC.DynamicAdaptationMode": 1,
"System.GC.HeapHardLimitPercent": 75
}
}
Discussion