C# > Memory Management > Garbage Collection > How Garbage Collection Works
Garbage Collection Demonstration with WeakReference
This snippet demonstrates how the Garbage Collector (GC) reclaims memory occupied by objects no longer in use, and how to use WeakReference
to observe object resurrection.
Code Snippet
This code creates a ResourceHeavyObject
that consumes a significant amount of memory (100MB in this example). After the object goes out of scope, GC.Collect()
is called to force garbage collection. GC.WaitForPendingFinalizers()
ensures that the finalizer method (~ResourceHeavyObject()
) is executed. A WeakReference
is used to track if the object has been collected by the GC. WeakReference
allows the object to be collected if no other strong references exist, unlike strong references that prevent collection.
using System;
public class ResourceHeavyObject
{
private byte[] data;
public ResourceHeavyObject(int sizeInMB)
{
data = new byte[sizeInMB * 1024 * 1024];
Console.WriteLine($"ResourceHeavyObject allocated {sizeInMB} MB.");
}
~ResourceHeavyObject()
{
Console.WriteLine("ResourceHeavyObject finalized.");
}
}
public class GarbageCollectionDemo
{
public static void Main(string[] args)
{
WeakReference weakRef = null;
{
ResourceHeavyObject obj = new ResourceHeavyObject(100); // Allocate 100MB
weakRef = new WeakReference(obj);
}
Console.WriteLine("Object out of scope, suggesting GC.");
GC.Collect(); // Force garbage collection
GC.WaitForPendingFinalizers(); // Wait for finalizers to complete
if (weakRef.IsAlive)
{
Console.WriteLine("Object still alive after GC.");
}
else
{
Console.WriteLine("Object collected by GC.");
}
Console.ReadKey();
}
}
Concepts Behind the Snippet
The .NET Garbage Collector (GC) automatically manages memory allocation and deallocation. It works by periodically identifying objects that are no longer reachable (i.e., no longer referenced by any live code) and reclaiming the memory they occupy. This process involves marking live objects and then sweeping away the unmarked ones. Finalizers are methods that are called just before an object is collected, allowing it to release resources. Using GC.Collect()
is generally discouraged in production code but is useful for demonstration and testing purposes. WeakReference
enables tracking of an object's lifetime without preventing it from being collected.
Real-Life Use Case Section
A real-life use case for WeakReference
is caching. Imagine a scenario where you have a cache of large images. You want to keep images in memory as long as possible to avoid reloading them from disk, but you also don't want to prevent the GC from reclaiming memory if memory becomes scarce. By using WeakReference
to store the image references in the cache, you allow the GC to collect the images if they are no longer in use, while still keeping them in memory if possible.
using System;
using System.Collections.Generic;
public class ImageCache
{
private Dictionary<string, WeakReference> cache = new Dictionary<string, WeakReference>();
public Image GetImage(string key, Func<string, Image> imageLoader)
{
if (cache.TryGetValue(key, out WeakReference weakRef))
{
if (weakRef.IsAlive)
{
return (Image)weakRef.Target; //Image fetched from cache
}
else
{
cache.Remove(key);
}
}
Image image = imageLoader(key); // Load image if not in cache
cache[key] = new WeakReference(image);
return image;
}
}
// Assume 'Image' class exists (e.g., System.Drawing.Image)
public class Image {}
Best Practices
GC.Collect()
frequently can negatively impact performance. Let the GC manage memory automatically.IDisposable
interface and use using
statements to ensure timely release of resources, especially unmanaged resources.
Interview Tip
Understanding how the GC works is a common interview topic for C# developers. Be prepared to discuss the different generations of the GC, the difference between managed and unmanaged resources, and how to optimize memory usage. Knowing about WeakReference
and when to use it is also valuable.
When to Use WeakReference
Use WeakReference
when you want to keep an object alive as long as possible without preventing it from being garbage collected. This is useful for caches, event handlers, and other scenarios where you want to avoid recreating objects unnecessarily but don't want to hold onto them indefinitely. Avoid using it when a strong reference is absolutely necessary, as the object might be collected unexpectedly.
Memory Footprint
The memory footprint of an application is the amount of memory it consumes. Garbage collection is crucial for managing this footprint, as it prevents memory leaks and reclaims unused memory. Monitoring memory usage and optimizing object allocation can significantly reduce the memory footprint of your application. Using techniques like object pooling and lazy initialization can also help.
Alternatives
Alternatives to relying solely on the GC for memory management include:
Pros
Cons
FAQ
-
What are the generations in the Garbage Collector?
The GC uses generations to optimize the collection process. New objects are allocated in Generation 0. If they survive a collection, they are promoted to Generation 1, and so on. Older generations are collected less frequently, as they are assumed to contain long-lived objects. This approach reduces the time spent collecting frequently created and destroyed objects. -
Why should I avoid calling GC.Collect() in production code?
CallingGC.Collect()
forces a garbage collection, which can be an expensive operation. The GC is designed to automatically collect memory when needed, so forcing a collection can disrupt its optimized schedule and lead to performance degradation. It is generally better to let the GC manage memory automatically. -
What is the difference between managed and unmanaged resources?
Managed resources are objects that are controlled by the .NET runtime and are automatically garbage collected. Unmanaged resources are resources that are not controlled by the .NET runtime, such as file handles, network connections, and pointers to memory allocated outside the .NET heap. Unmanaged resources must be explicitly released by the developer, typically using theIDisposable
interface and theDispose()
method.