Garbage Collection (GC) in C#
Garbage Collection (GC) in C# is an automated memory
management mechanism provided by the Common Language Runtime (CLR) in
the .NET framework. It eliminates the need for developers to manually allocate
and free memory, which helps reduce errors such as memory leaks or improper
resource cleanup, dangling pointers, or double deletion.
Purpose of Garbage Collection
- Automatic
Memory Management: GC relieves developers from manually deallocating
memory, reducing common errors like dangling pointers, memory leaks, or
double-free errors.
- Efficient
Resource Utilization: It ensures memory is available for the
application by reclaiming unused objects.
Key Features of GC
- Automatic
Memory Management:
- GC
automatically deallocates objects no longer in use.
- Developers
only focus on memory allocation, while GC handles deallocation.
- Managed
Heap:
- All
memory allocated for managed objects resides in the managed heap.
- The
heap is organized into generations to optimize performance.
- Minimizing
Fragmentation:
- GC
compacts memory by relocating objects in the heap, reducing
fragmentation.
How GC Works: Garbage collection is based on the mark-and-sweep
algorithm:
- Mark
Phase:
- Identifies
all objects still accessible by the application (reachable objects).
- Unreachable
objects are considered garbage.
- Sweep
Phase:
- Reclaims
memory occupied by unreachable objects.
- Memory
is compacted to avoid fragmentation (if necessary).
- Compaction:
- Rearranges
remaining objects in memory to eliminate gaps caused by removed objects.
Generations in GC
To optimize performance, the GC categorizes objects into generations
based on their lifespan. The heap is divided into three generations:
- Generation
0:
- Holds
short-lived objects (e.g., temporary variables, newly allocated objects).
- Collected
most frequently, as short-lived objects are often garbage quickly.
- Generation
1:
- Acts
as a buffer between short-lived and long-lived objects.
- Contains
objects that survived Generation 0 collections.
- Generation
2:
- Contains
long-lived objects (e.g., static data, objects held for the app's
lifetime).
- Collected
less frequently.
Why Generations?
- Frequent
collections of Generation 0 are faster since it has fewer objects.
- Minimizes
the overhead for long-lived objects in Generation 2.
Benefits of Generations:
- Reduces
the cost of GC by focusing on short-lived objects in Generation 0.
- Long-lived
objects are collected less often, improving performance.
Conditions for Triggering Garbage Collection (GC)
Garbage collection occurs under specific conditions:
- Low
Memory: When the system runs low on managed heap memory.
- Exceeding
Generation 0 Limits: When the memory in Generation 0 exceeds its
threshold.
- Explicit
Call: Using GC.Collect() (not recommended unless necessary).
- Application
Idle Time: GC may run during idle times to optimize memory.
Managed Heap
- Memory
allocated for managed objects resides in the managed heap.
- The
managed heap is divided into three generations, and the GC manages them
using the principles mentioned above.
Finalization and IDisposable
Objects holding unmanaged resources (e.g., file handles,
database connections) require special handling:
- Finalizer
(~ClassName):
- Called
by the GC before reclaiming an object's memory.
- Not
deterministic; you cannot control when it runs.
- IDisposable
Interface:
- Implements
a Dispose method for deterministic cleanup of unmanaged resources.
- Used
with using blocks for immediate resource disposal.
Example:
Key GC Methods in .NET
- GC.Collect():
- Forces
a garbage collection.
- Avoid
excessive use; let the GC work automatically.
- GC.GetTotalMemory(bool
forceFullCollection):
- Returns
the total memory used by the managed heap.
- GC.SuppressFinalize(object
obj):
- Prevents
the finalizer from being called.
- GC.WaitForPendingFinalizers():
- Pauses
execution until all finalizers complete.
Best Practices
- Minimize
Allocations:
- Avoid
creating unnecessary objects, especially in loops.
- Use
using Statements:
- Ensure
proper disposal of unmanaged resources.
- Avoid
Explicit GC.Collect():
- Trust
the GC to manage memory efficiently.
- Implement
IDisposable:
- For
classes holding unmanaged resources, implement the IDisposable pattern.
- Use
Value Types for Short-Lived Objects:
- Prefer
stack allocation when possible (e.g., Span<T>).
Advantages of GC
- Simplifies
memory management.
- Reduces
memory leaks and dangling pointer issues.
- Provides
optimized memory utilization through generations.
Limitations of GC
- No
control over when garbage collection occurs.
- May
cause performance hiccups (pauses) during large collections.
- Overhead
for managing generations and compaction.