I often get asked about the garbage collector (GC) in the .NET Compact Framework. What is this magic thing that handles all my memory for me? Why do I run into out-of-memory situations and GC.Collect sometimes helps? In the past, I have spent some time describing the GC in my performance talks that I have given and I have always thought of it as almost too meaty. However, people are out there writing huge applications and stressing the memory systems to the max (and believe me, I am not complaining about this!). In this article, I continue on this trend to describe the GC in the .NET Compact Framework and when using GC.Collect is appropriate.
Steven Pratschner has also written a great article that covers a lot of this and how the .NET Compact Framework garbage collector operates: An Overview of the .Net Compact Framework Garbage Collector
Please note that the .NET Compact Framework garbage collector is significantly different than the full .NET Framework. However, the advice for GC.Collect generally remains the same. Please see the following articles from Rico Mariani pertaining to GC.Collect and the full .NET Framework: Two things to avoid for better memory usage and When to call GC.Collect().
Of course, you should re-read Mike Zintel’s article .Net Compact Framework Advanced Memory Management and read his later post "Reaching" Out to 'C' Developers (How I Learned to Love Garbage) which describes “reachability” in a garbage collected runtime.
First off, a couple of definitions:
Now, why you shouldn’t call GC.Collect:
· The time it takes the CLR to do a GC is a function of both the number of live and dead objects. A GC consists of the following phases:
· The CLR is self-tuning and knows best when to run a GC. The following triggers are used to invoke a GC in v1:
· Since an application does not have access to the triggers described above, it is difficult to predict when GC.Collect should be called. Consider the following scenario:
· Sometimes, GC.Collect is called to force finalizers to be run for dead objects and thus free native code resources or memory. Instead, it is better to implement the IDisposable pattern for objects that have finalizers and then chain the IDisposable pattern up the object chain to the root object. This way, when the application is finished using the root object, it can call Dispose(), which will call the Dispose() methods down the chain to the leaf object and free the unmanaged resource. The garbage collector will handle the rest.
If you must call GC.Collect, then here are the rules that I would like you to follow:
Here are a couple of samples snippets to show you what I mean. The first is a real piece of code that I used in an application that I built. The application was trying to load a (fairly large) Bitmap from a stream after just releasing the reference to another. The v1 of the .NET Compact Framework does not retry on the CreateDIBSection failure in native code, so this allowed my Bitmap to display correctly if memory was returned to the OS.
try {
// Bitmap constructor throw an exception if OOM
bitmap = Bitmap(stream);
} catch {
// Generally, you should try to catch an explicit
// exception, but I know that this can throw 2 different
// types of exceptions on an OOM.
GC.Collect();
// An exception will be thrown if still OOM
}
Here is another sample snippet for an application defined unmanaged resource.
class MyObject {
IntPtr unmanagedResource;
[DllImport(“myassembly.dll”)]
private static extern IntPtr GetUnmanagedResource();
public MyObject()
{
// P/Invoke allocates the unmanaged resource and returns
// null if allocate of the unmanaged object fails.
unmanagedResource = GetUnmanagedResource();
// Did the allocation fail?
if (unmanagedResource == IntPtr.Zero) {
// Retry...
// If failed again, propagate the OOM.
throw new OutOfMemoryException();
o GC.Collect may not be able to free (and compact) enough memory in order to return memory to the operating system in order for the unmanaged resource allocate to succeed on the second pass.
o Notes:
o You should never need to do this for objects that are strictly managed or if the CLR has triggers associated with the resource.
o This typically should only be done for large resources, certainly not on the order of 100 bytes.
o This restriction of tying it to a user event typically prevents a case of GC.Collect being called in a loop that will cause performance issues.
What we have improved in v2.0 of the .NET Compact Framework:
Scott
This posting is provided "AS IS" with no warranties, and confers no rights.