DEV Community

Libin Tom Baby
Libin Tom Baby

Posted on

Understanding Garbage Collection (GC) in .NET — How It Works and When It Matters


Garbage Collection (GC) in .NET

Memory management is one of the most important concepts in .NET. Developers often say “the Garbage Collector handles memory for you,” but few can clearly explain how it works, why it exists, and what you can do to work with it instead of against it.

This guide breaks down .NET’s Garbage Collection (GC) in a simple, practical way — with definitions, diagrams, examples, and real-world scenarios.


What Is Garbage Collection?

Garbage Collection (GC) is an automatic memory management system in .NET.

Its job is to:

  • Allocate memory for new objects
  • Track which objects are still in use
  • Free memory for objects that are no longer needed
  • Prevent memory leaks
  • Reduce developer errors like dangling pointers

In short: GC keeps your application healthy by cleaning up unused objects automatically.


How GC Works in .NET

The .NET GC uses a generational, mark‑and‑compact algorithm.

Let’s break that down.


1. Generations (Gen 0, Gen 1, Gen 2)

Objects are grouped into generations based on their lifetime.

Generation 0

  • Newly created objects
  • Collected frequently
  • Fastest to clean

Generation 1

  • Surviving objects from Gen 0
  • Medium‑lived objects

Generation 2

  • Long‑lived objects
  • Large objects
  • Cleaned infrequently

Why generations?

Most objects die young.

So GC optimizes for that.


2. Mark Phase

GC pauses the application briefly and marks all objects that are still reachable.

Reachable means:

  • Local variables
  • Static references
  • Objects referenced by other objects

Everything else is considered garbage.


3. Compact Phase

After marking, GC compacts memory by moving surviving objects together.

This reduces fragmentation and improves performance.


4. Large Object Heap (LOH)

Objects > 85 KB go to the Large Object Heap.

  • Not compacted often (expensive to move large objects)
  • Can cause fragmentation
  • Avoid unnecessary large allocations

When Does GC Run?

GC runs automatically when:

  • Memory is low
  • Gen 0 fills up
  • The system is under pressure
  • You explicitly call GC.Collect() (not recommended)

Should You Call GC.Collect()?

Almost never.

Calling it manually:

  • Forces a full collection
  • Pauses your app
  • Hurts performance
  • Breaks GC’s optimization logic

Use it only in rare scenarios:

  • After a massive one‑time memory release
  • In controlled environments (e.g., game engines)

How to Work With GC (Best Practices)

✔️ Use using statements for disposable objects

using (var stream = new FileStream("data.txt", FileMode.Open))
{
    // work with stream
}
Enter fullscreen mode Exit fullscreen mode

This ensures deterministic cleanup.


✔️ Avoid unnecessary large object allocations

var bigArray = new byte[100_000]; // Goes to LOH
Enter fullscreen mode Exit fullscreen mode

Reuse buffers when possible.


✔️ Prefer Span<T> and Memory<T> for high-performance scenarios

These avoid heap allocations.


✔️ Avoid long-lived references

If you keep references alive, GC cannot collect them.


✔️ Use dependency injection wisely

Singletons live for the entire app lifetime — avoid storing large objects inside them.


Real‑World Scenarios

Scenario 1: Web API handling thousands of requests

Short-lived objects → Gen 0

GC handles them efficiently.


Scenario 2: Image processing

Large byte arrays → LOH

Avoid repeated allocations; use pooling.


Scenario 3: Background services

Long-lived services → Gen 2

Be careful with memory leaks.


Interview‑Ready Summary

  • GC automatically manages memory in .NET
  • Uses generational collection (Gen 0, 1, 2)
  • Uses mark and compact algorithm
  • LOH stores large objects
  • Avoid calling GC.Collect() manually
  • Use using, pooling, and DI best practices

This explanation shows you understand both the theory and the practical implications.


Bonus thought

Is IDisposable Related to Garbage Collection? Clearing the Confusion

Many developers — especially those new to .NET — assume that IDisposable is part of the Garbage Collector. It’s a common misunderstanding, but the two concepts solve different problems.

Garbage Collection handles managed memory

GC automatically frees memory for objects stored on the managed heap.

Examples:

  • Classes
  • Strings
  • Arrays
  • Delegates

GC decides when to clean them up.

IDisposable handles unmanaged resources

IDisposable exists because GC cannot clean up unmanaged resources, such as:

  • File handles
  • Database connections
  • Network sockets
  • OS handles
  • Streams
  • Native memory

These require deterministic cleanup, which is why we use:

using (var stream = new FileStream("data.txt", FileMode.Open))
{
    // work with stream
}
Enter fullscreen mode Exit fullscreen mode

How they relate

  • GC cleans up managed memory
  • IDisposable cleans up unmanaged resources
  • Finalizers (~ClassName) act as a safety net, but they are expensive
  • The using statement ensures cleanup happens immediately, not “whenever GC runs”

The rule of thumb

GC ≠ Dispose

GC frees memory

Dispose frees resources

This distinction is crucial in interviews and real-world systems.


Top comments (0)