Garbage Collection (GC) in C# is an automatic memory management feature provided by the Common Language Runtime (CLR). Its primary goal is to free memory occupied by objects that are no longer in use, preventing memory leaks and improving application stability.
Unlike languages like C or C++, developers do not manually free memory in C#. The CLR’s garbage collector handles it automatically.
When Garbage Collection Is Needed?
Generation in Garbage Collection.
The C# Garbage Collector uses a generational model based on a simple but powerful assumption:
Most objects are short-lived. To optimize performance, objects are grouped into generations based on their longevity. Each generation is collected at different frequencies.
Most objects are short-lived. To optimize performance, objects are grouped into generations based on their longevity. Each generation is collected at different frequencies.
1. Generation 0 (Gen 0)
Generation 0 contains newly created objects. Almost every object starts its life here. It is collected very frequently because most objects become unused quickly. A Gen 0 collection is fast and inexpensive.
If an object survives a Gen 0 collection, it is promoted to Generation 1.
If an object survives a Gen 0 collection, it is promoted to Generation 1.
Example:
void CreateObjects() { int x = 10; var temp = new object(); // Gen 0 object }
Typical Gen 0 objects:
- Temporary objects
- Method-local objects
- Short-lived calculations
2. Generation 1 (Gen 1)
Generation 1 acts as a buffer between Gen 0 and Gen 2. It contains objects that survived at least one garbage collection. Objects in Gen 1 are collected less frequently than Gen 0. If they survive a Gen 1 collection, they are promoted to Generation 2.
Example:
class CacheItem { public string Data { get; set; } } CacheItem item = new CacheItem(); // Starts in Gen 0
Typical Gen 1 objects:
- Objects with medium lifetime
- Temporary caches
- Objects shared across a few methods
3. Generation 2 (Gen 2)
Generation 2 contains long-lived objects that remain in memory for a long time. Gen 2 collections are infrequent and expensive, but they clean a large portion of memory. Objects in Gen 2 usually stay there until the application ends or the reference is explicitly removed.
Example:
class AppConfig { public static AppConfig Instance = new AppConfig(); }
Typical Gen 2 objects:
- Static objects
- Singletons
- Long-lived caches
- Application-wide configuration data
Large Object Heap (LOH)
Objects larger than 85,000 bytes are allocated on the Large Object Heap, which is logically part of Gen 2.
- LOH objects are collected only during Gen 2 GC
- Not compacted by default (to avoid performance cost)
Example:
byte[] largeArray = new byte[100_000]; // LOH
In short:
- Gen 0 collections are fast and frequent
- Gen 1 filters objects before reaching Gen 2
- Gen 2 collections are rare but thorough
- Improves overall performance and responsiveness
| Generation | Lifetime | Collection Frequency |
|---|---|---|
| Gen 0 | Short-lived | Very frequent |
| Gen 1 | Medium-lived | Less frequent |
| Gen 2 | Long-lived | Rare |
| LOH | Very large objects | During Gen 2 |
How Garbage Collection is Triggered Automatically?
When a C# application is running, objects are constantly being allocated on the managed heap. The CLR keeps track of these allocations and monitors the available free memory. Garbage Collection is automatically triggered when the CLR detects that continuing allocations without cleanup would negatively impact performance or stability.
The most common automatic triggers are:
- The managed heap does not have enough free space for new allocations
- Allocation pressure becomes high (too many objects being created quickly)
- A generation threshold is exceeded (Gen 0, Gen 1, or Gen 2)
- The system is under memory pressure (low available RAM)
- The application enters certain runtime phases (for example, during idle time)
- At this point, the CLR decides on its own that a garbage collection cycle is required and pauses the application briefly to perform it.
How To Manually Call Garbage Collection in C#?
First of all, it is never recommended to call GC manually by a developer, and it is completely handled by CLR. However, there are rare, controlled scenarios where forcing garbage collection can make sense.
Example: Imagine a console application that processes a huge amount of data, creates large objects, and then finishes its work.
using System; using System.Collections.Generic; class Program { static void Main() { Console.WriteLine("Application started"); CreateLargeObjects(); Console.WriteLine("Large objects created and released"); // Force garbage collection GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.WriteLine("Garbage collection completed"); Console.ReadLine(); } static void CreateLargeObjects() { List<byte[]> largeData = new List<byte[]>(); for (int i = 0; i < 10; i++) { // Each array is ~10 MB largeData.Add(new byte[10_000_000]); } // Remove references largeData = null; Console.WriteLine("References removed"); } }
- The application allocates large objects (LOH).
- References are explicitly removed.
- GC.Collect() forces garbage collection.
- GC.WaitForPendingFinalizers() ensures finalizers run.
- Memory is reclaimed immediately.
This is a controlled environment, where the developer knows exactly when memory is no longer needed.
What is the use of Dispose() method in C#?
The Dispose() method is used to explicitly and immediately release unmanaged resources such as file handles, database connections, network sockets, or native memory. It provides deterministic cleanup, meaning the developer controls when the resources are freed instead of waiting for the Garbage Collector.
Garbage Collection only frees managed memory. It does not know how or when to release unmanaged resources. If these resources are not released promptly, the application can run out of OS-level resources even if enough memory is available.
Dispose() solves this by allowing the developer to clean up resources as soon as they are no longer needed.
Example:
class FileLogger : IDisposable { private StreamWriter _writer = new StreamWriter("log.txt"); public void Write(string message) { _writer.WriteLine(message); } public void Dispose() { _writer.Dispose(); // releases file handle immediately Console.WriteLine("Resources released using Dispose()"); } }
using (var logger = new FileLogger()) { logger.Write("Hello"); } // Dispose() is called automatically here
- Dispose() releases unmanaged resources immediately.
- It is called explicitly by the developer or via the using keyword.
- Provides predictable and safe cleanup.
- Preferred over Finalize() for resource management.
Dispose() is used to deterministically release unmanaged resources as soon as they are no longer needed, improving performance and reliability.
What is the use of Finalizer in C#?
The Finalize method (finalizer) is used to clean up unmanaged resources if they were not released explicitly using Dispose(). It acts as a last-resort safety mechanism and is automatically called by the Garbage Collector before an object’s memory is reclaimed.
Example:
class NativeResource { ~NativeResource() { // Release unmanaged resource Console.WriteLine("Finalize called"); } }
Best Practices for Garbage Collection
- Avoid unnecessary object creation
- Reuse objects when possible
- Dispose of unmanaged resources properly
- Avoid manual GC.Collect()
- Use using for streams, files, and DB connections
- Be careful with static references

No comments
Post a Comment