# The CLR and Garbage Collection

## 1. What the CLR Does

The Common Language Runtime runs .NET code. C# compiles to **IL** (intermediate language) plus metadata in an assembly; at runtime the **JIT compiler** turns IL into machine code method by method (with **tiered compilation**: a quick tier-0 version first, then an optimized tier-1 version for hot methods, guided by profiling). The CLR also provides type safety, exception handling, threads, and **automatic memory management**.

```text
C# source → Roslyn compiler → IL + metadata (.dll) → JIT (tier 0 → tier 1, PGO) → machine code
                                                    └ or ReadyToRun / Native AOT: compiled ahead of time
```

**ReadyToRun** precompiles IL to native code to speed up startup (still JITs hot paths later). **Native AOT** compiles everything ahead of time into a self-contained executable: very fast startup and small memory, but no runtime code generation, so reflection-heavy code needs care.

## 2. The Managed Heap and Generations

Reference-type objects live on the managed heap. The GC is **generational**, based on the observation that most objects die young:

| Generation | Contains | Collected |
|---|---|---|
| **Gen 0** | Newly allocated objects | Very often, very cheaply |
| **Gen 1** | Survivors of one gen-0 collection (a buffer) | Often |
| **Gen 2** | Long-lived objects (caches, singletons) | Rarely; a full (expensive) collection |
| **LOH** (Large Object Heap) | Objects ≥ 85,000 bytes | Only with gen 2; not compacted by default |
| **POH** (Pinned Object Heap) | Objects allocated as pinned | Avoids fragmenting other heaps |

Allocation is just bumping a pointer, so it's very fast. A collection **marks** objects reachable from **roots** (static fields, locals on thread stacks, CPU registers, GC handles), then **sweeps/compacts** the rest. Survivors get promoted to the next generation.

## 3. Workstation vs Server GC, Concurrent Collection

- **Workstation GC**: one heap, tuned for responsiveness of desktop apps.
- **Server GC**: one heap and GC thread per core, much higher throughput; the default for ASP.NET Core.
- **Background (concurrent) GC** collects gen 2 while application threads keep running, with only short pauses.
- **DATAS** (dynamic adaptation, .NET 8+) lets Server GC shrink and grow the number of heaps to save memory in containers.

Settings live in the project file or `runtimeconfig.json` (`<ServerGarbageCollector>`, `<ConcurrentGarbageCollection>`), and the GC respects **container memory limits**.

## 4. Deterministic Cleanup: IDisposable

The GC only manages memory. **Unmanaged resources** (file handles, sockets, database connections) must be released explicitly:

```csharp
using var stream = File.OpenRead(path);   // Dispose() called at end of scope, even on exceptions
await using var conn = new NpgsqlConnection(cs);  // IAsyncDisposable
```

- Implement `IDisposable` when your class **owns** a disposable resource; dispose what you own, not what you were given.
- **Finalizers** (`~MyType()`) run on a finalizer thread at some unknown time after an object becomes unreachable; they delay collection by a generation and should only exist as a safety net for raw unmanaged handles. Prefer `SafeHandle`.
- `GC.SuppressFinalize(this)` in `Dispose()` skips the finalizer when cleanup already happened.

## 5. Memory Leaks in a Managed Language

The GC can't free objects that are still **reachable**. Typical leaks:

- **Event handlers**: a long-lived publisher's event holds references to every subscriber that never unsubscribes.
- **Static collections and caches** that only grow (use bounded caches like `MemoryCache` with size limits/expiry).
- **Captured closures** and timers that keep objects alive.
- **Captive dependencies** in DI: a singleton holding scoped objects.

Diagnose with `dotnet-counters` (heap size, gen counts, allocation rate), `dotnet-gcdump`/`dotnet-dump` and heap analysis in Visual Studio, Rider or PerfView: compare snapshots and look at **retention paths**.

## Common Pitfalls

1. **Calling `GC.Collect()`** to "fix" memory: it usually makes things slower; find the leak instead.
2. **Not disposing** `HttpResponseMessage`, streams, DB connections, `CancellationTokenSource` with timers.
3. **Creating `HttpClient` per request** (socket exhaustion); use `IHttpClientFactory`.
4. **Allocating large buffers repeatedly** (LOH churn); pool them with `ArrayPool<T>`.
5. **Finalizers on ordinary classes**: they slow the GC for no benefit.

## Quick Reference

```text
C# → IL → JIT (tiered, PGO) · ReadyToRun = faster startup · Native AOT = no JIT, limited reflection
Gen0 (young, cheap) → Gen1 → Gen2 (full GC) · LOH ≥ 85,000 B · POH for pinned
Server GC (ASP.NET default) · background GC for gen2 · DATAS adapts heap count
IDisposable/using for unmanaged resources · finalizers only as a safety net (SafeHandle)
leaks = still reachable: events, static caches, closures, captive DI deps · dotnet-counters / gcdump
```
