DATAS and Server Garbage Collection in .NET 9

Sven Hennessen

.NET 9 brings numerous optimizations regarding performance and resource management. One of the most important innovations is DATAS (Dynamic adaptation to application sizes), which is now enabled by default. DATAS improves memory management by dynamically adapting heap size to workload and available resources.

What is DATAS?

DATAS is a Garbage Collector optimization that was introduced as an optional feature in .NET 8 and is enabled by default in .NET 9.

Unlike the classic Server GC, which is optimized for maximum throughput, DATAS dynamically adapts heap size and GC strategies to the actual requirements of the application. This is particularly advantageous with fluctuating loads and in container environments, as memory consumption can be controlled more efficiently.

For more details about how DATAS works, check out the official documentation.

Test Setup

To demonstrate the improvements through DATAS, we created a test setup with the following components:

  • Minikube as local Kubernetes cluster
  • An ASP.NET service that allocates large amounts of memory
  • OpenTelemetry for monitoring
  • Prometheus as time series database
  • Grafana for visualization
  • k6 for automated load tests

The service stores large strings for each business object in memory and deletes them again after some time.

⚠️ Note: The service stores personal data. It does this only because the author couldn't think of a better example and it should serve as a reminder that you should NEVER DO THIS CARELESSLY 😉.

You can find the complete source code here: GitHub

Installation and Setup

# Start Minikube
minikube start --extra-config=kubelet.authentication-token-webhook=false --extra-config=kubelet.authorization-mode=AlwaysAllow

# Deploy monitoring stack
kubectl apply -f kubectl/observability/

# Deploy and test service
make serve_dotnet8  # for .NET 8
# Copy URL and port
k6 run k6/test.js -e MAX_USERS=50 -e BASE_URL=<URL:PORT>
# Wait for test
make serve_dotnet9  # for .NET 9 
# Copy URL and port
k6 run k6/test.js -e MAX_USERS=50 -e BASE_URL=<URL:PORT>

Observations

In our tests, we compared the behavior under load with k6 and visualized it with a Grafana dashboard.

Memory Allocations Graph

Clear differences between .NET 8 and .NET 9 become apparent:

Memory Allocation

Memory Allocations Graph

The graph shows that .NET 9 with DATAS exhibits more controlled growth of allocations. The blue line (.NET Allocations) rises more gradually compared to the steep steps in .NET 8.

Heap Size

Heap Size Graph

The behavior of the Large Object Heap (LOH - orange line) is particularly interesting:

  • .NET 8 shows larger, step-wise increases
  • .NET 9 shows more gradual growth
  • Adaptation to load occurs more dynamically

Garbage Collection Runtime

Garbage Collection Runtime Graph

.NET 9 shows:

  • More frequent but shorter GC spikes
  • More regular Gen0 collections (green line)
  • Overall better distribution of GC pauses

Number of Stored Objects

Number of Stored Objects Graph

The number of stored objects grows similarly in both versions, confirming the comparability of the tests.

Conclusion

The results clearly show the advantages of DATAS in .NET 9:

  1. More efficient memory usage during load peaks
  2. Better adaptation to changing workloads
  3. Controlled growth of the Large Object Heap

These improvements are particularly relevant for containerized microservices, as they lead to better resource utilization, more stable performance, and lower average operating costs.

Have you already migrated your Kubernetes workloads to .NET 9 and seen improvements in memory utilization with DATAS enabled? Write to us! We look forward to exchanging experiences with you.

Never miss an article

No spam. Only relevant news about and from us. Unsubscribe anytime.