Introduction
Managed applications and garbage collection advertise that the developer need not care about memory allocations and object lifetime, leaving them to the mercy of Garbage Collector. The JIT compiler has a mind of its own and optimizes the code under the hood. This veils the developer from the object's moment of abrogation. Although we do not know the moment of an object's destruction, careful planning and design are required to orchestrate with the Garbage Collector to avoid pitfalls.
The Problem
Guessing when and where the object is marked for garbage collection is tricky. Consider the following code :
namespace NS
{
class A
{
public static void Main()
{
System.String s;
s = "some test string";
System.Int32 i;
i = 10;
}
}
}
The place where string s
is marked for garbage collection by JIT is not known exactly. This is especially a problem when we try to control objects which are required throughout the life of an application.
Sample solution
A common example that is quoted is a mutex which is used to make sure that only one instance of the application is running. The following code illustrates this:
namespace NS
{
class SomeFormClass:System.Windows.Forms.Form
{
public static void Main()
{
bool created;
System.Threading.Mutex m = new
System.Threading.Mutex(true, "OnlyOneApp", out created);
if (! created)
return;
System.Windows.Forms.Application.Run(new SomeFormClass());
System.GC.KeepAlive(m);
}
}
}
If the GC.KeepAlive()
statement is missing, the JIT compiler might optimize and mark the mutex for garbage collection before the Application.Run()
statement. The GC.KeepAlive()
statement keeps the object alive upto that statement. In the above scenario, we know the exact place where the mutex is created and till where it is required. Another point is that the variable is accessible at both these known points. Consider an example where an object is required throughout the application but its instantiation point is unknown. Also, it is assumed that no single "live" object may be holding a reference to this required object throughout the life of the application. This scenario prompts the JIT to mark the object for garbage collection at the earliest point where the object goes out of scope. The code below illustrates a design to keep this kind of objects alive and also points out how to handle the situation in Console and Windows applications. It is most important to use the GC.SuppressFinalize()
method at the end when the application exists, otherwise the application will go into an infinite loop and hangs.
namespace GCDemo
{
class StartTest
{
internal delegate void ApplicationExit();
static internal event ApplicationExit AppExit;
public static void Main()
{
for (int i=0; i<3; i++)
{
new GCDemo();
}
if (AppExit != null)
AppExit();
}
}
public class GCDemo
{
public GCDemo()
{
StartTest.AppExit += new StartTest.ApplicationExit(AppExiting);
}
~GCDemo()
{
System.GC.ReRegisterForFinalize(this);
}
void AppExiting()
{
System.GC.SuppressFinalize(this);
}
}
}
Sometimes, it is worth calling GC.Collect()
and GC.WaitForPendingFinalizers()
like shown below before allocating huge memory, especially when there is a crunch on memory.
......
GC.Collect();
GC.WaitforPendingFinalizers();
for (int i=0; i<1000; i++)
{
somelist.Add(new someobject());
}
......