C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
87: Server GC vs Workstation GC
You’ve probably spent most of your time in C# focusing on what you're allocating, but rarely on how the runtime decides to clean it up. For a long time, the default settings just "work." But once you move from a desktop utility to a high-traffic API, or from a microservice to a massive data-processing engine, you'll hit a wall where the Garbage Collector (GC) becomes your biggest bottleneck. I've seen developers spend weeks optimizing LINQ queries only to realize the real culprit was that they were running a high-throughput server on Workstation GC settings.
The trap of the "responsive" collector
By default, if you're running a client application, .NET uses Workstation GC. It's designed for a very specific user experience: responsiveness. The goal here is to avoid those annoying "stutters" in a UI. To achieve this, the GC tries to keep pause times short, often performing collections on a single thread or using background threads that don't hog the CPU. It's great for a Windows Forms app or a small tool, but it's a disaster for a server handling 10,000 requests per second.
If you leave a high-load API on Workstation GC, you'll notice a strange phenomenon. Your CPU usage might look low—maybe only 20%—but your request latency is spiking. This happens because the GC is struggling to keep up with the allocation rate on a single heap. It's like trying to empty a swimming pool with a bucket while a fire hose is filling it up. Eventually, the GC has to stop everything for a "full" collection, and your API just freezes for a few hundred milliseconds. In a microservices architecture, that's enough to trigger a timeout in a calling service and start a cascading failure.
Trading latency for raw throughput
This is where Server GC comes in. When you flip the switch to Server GC, .NET changes its entire strategy. Instead of one heap, it creates a separate heap and a separate GC thread for every logical processor on your machine. If you're on a 16-core server, you now have 16 heaps working in parallel.
The trade-off is a classic engineering compromise: throughput vs. latency. Server GC is designed to move as much memory as possible, as fast as possible. It doesn't care as much about a tiny stutter in a UI because there is no UI. It cares that the total amount of time the CPU spends cleaning memory is minimized so that more cycles can go toward processing requests. However, Server GC is "hungrier." It will claim more memory from the OS and hold onto it longer than Workstation GC would. If you run a tiny console app in Server GC mode on a massive machine, you'll be baffled why a "Hello World" program is suddenly eating 500MB of RAM.
Deciding which lever to pull
I usually tell my team to follow a simple rule of thumb: if it's a service that lives in a container or on a server and is expected to handle concurrent load, use Server GC. If it's a tool that runs on a user's laptop, stick with Workstation.
You can control this in your .csproj file or your runtimeconfig.json. The naive approach is to just leave it to the environment, but being explicit prevents surprises when you move from a 4-core dev machine to a 64-core production cluster.
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
Just be careful. If you're deploying to a Kubernetes cluster with tight memory limits (say, 256MB), Server GC can actually be a liability. Because it allocates multiple heaps, it can hit that memory limit much faster than Workstation GC, leading to an OOM (Out of Memory) kill by the orchestrator. In those specific "tight-memory" cloud scenarios, I've actually gone back to Workstation GC or tuned the gcHeapHardLimit to keep the runtime from getting too greedy.
📋 Practical Task
Configuring a High-Throughput Telemetry Processor
You are building a telemetry ingestion service that processes millions of small objects per minute. Currently, the service is running with default settings on a 16-core machine, but you're seeing significant "Stop-the-World" pauses that are causing request timeouts.
Your Task:
- Modify the project configuration to explicitly enable Server GC.
- Assuming this service will be deployed to a Docker container with a strict memory limit of 1GB, add the configuration setting to the
.csprojorruntimeconfig.jsonthat prevents the GC from exceeding a specific heap hard limit (set it to 800MB to leave room for the OS). - Write a brief comment in your file explaining why you chose Server GC over Workstation GC for this specific telemetry use case.
There are no comments for now.