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.

Signature
DOTNET_gcServer=1  |  <ServerGarbageCollection>true</ServerGarbageCollection>  |  "System.GC.Server": true
  • Added in .NET .NET Core 1.0

Parameters

ParameterTypeDescription
$DOTNET_gcServeroptional0 or 1= 1 for Microsoft.NET.Sdk.Web projects, 0 otherwiseServer (1) or Workstation (0). Equivalent to System.GC.Server in runtimeconfig.json and ServerGarbageCollection in the project file.
$DOTNET_gcConcurrentoptional0 or 1= 1Background (concurrent) gen2 collections. System.GC.Concurrent / ConcurrentGarbageCollection.
$DOTNET_GCHeapHardLimitoptionalhex bytes= 75% of the container memory limit when one is setMaximum GC heap size, for example 0x20000000 for 512 MB. System.GC.HeapHardLimit.
$DOTNET_GCHeapHardLimitPercentoptionalhex percent= 0x4B (75) inside a memory-limited containerHeap limit as a percentage of available memory, for example 0x46 for 70%. System.GC.HeapHardLimitPercent.
$DOTNET_GCHeapCountoptionalhex count= logical CPU countNumber of Server GC heaps; caps heaps (and GC threads) below the CPU count.
$DOTNET_GCDynamicAdaptationModeoptional0 or 1= 1 with Server GC in current .NETDATAS: 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 GCWorkstation GC
Default forASP.NET Core (Web SDK) projectsConsole apps, workers on Microsoft.NET.Sdk
Heaps and GC threadsOne per logical CPU (honours container CPU limits)One
ThroughputHigher, collections run in parallelLower
MemoryLarger gen0 budgets per heap; higher baselineSmall footprint
Best forBusy APIs with 2+ coresMany 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · xml
<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.

Example · yaml
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.

Example · json
{
  "configProperties": {
    "System.GC.Server": true,
    "System.GC.Concurrent": true,
    "System.GC.DynamicAdaptationMode": 1,
    "System.GC.HeapHardLimitPercent": 75
  }
}

Discussion

  • Be the first to comment on this lesson.