Saturday, November 8, 2008

The garbage collector and memory usage in .NET

During the last weeks I have been diving into the darkest corners of .NET. Normally you don't pay that much attention to the garbage collector in .NET. When it works and you haven't done anything that messes this up it is just sweet. You do not have to release used objects the GC does this for you. But there are times when this is not the case. There are some obvious traps you can fall into and some a bit more obscure. I'll devide this into two parts, the obvious and the sneaky.

I'll start with a breef introduction to the Garbage Collector (GC).
The GC manages the allocation and release of memory for your application. It waches over your memory usage and when you are using too much memory it will release 'dead' objects from the managed heap. Objects are structured in 3 generations + the large objects heap.

Generations explained:

The collector only runs when a certain amount of memory has been used or there is enough pressure for memory on the system.
.Net uses generations to optimize the garbage collector. There are 3 primary generations, 0 through 2.
All new objects are stored in gen 0. if an object survives a collection of gen 0 objects it is moved to the next generation, which is gen 1 in this case. by moving older objects to the next generation, since these objects are likely to be long living objects, this reduces the number of objects the GC needs to check in each collection.
During collection the GC removes and compacts the memory heap for the given generation. This is done by using memcpy to copy them over to the free space to make them contiguous again.

The fact that .NET is a garbage collected runtime you cannot jus look at the memory usage in task manager to determine the actual memory usage.

You can force a collection programmatically, this is however not recommended since the GC is optimized. Calling this yourself might cause your application to spend more time in garbage collection than necessary. There are cases that this might be a good idea. But the general recommendation is 'don't'.


The obvious:

If a class implements the IDisposable you should call it. If you do not have control over your objects life cycle implement a destructor to release native resources. with native resoures we normally think of com objects and pInvoke. But there are classes in .NET that uses native resources, and you MUST release these.
An example of classes that uses native resources is the System.IO namespace. Most streams has native resources bound to it. This is why it is is important to call .Close() and .Dispose() on such objects. Batabase connections is also something you must close as fast as possible.

Methods that uses such resources should always have the finally operator at the end.

internal long GetDASyncStatusCode(long daId)
{
System.Data.OracleClient.OracleCommand cmd = new OracleCommand(string.Format("SELECT SYNCSTATUS FROM DA WHERE DAID={0}", daId.ToString()), this._mainConnection);
cmd.Connection.Open();
long code = 1;
System.Data.OracleClient.OracleDataReader dr = null;
try
{
dr= cmd.ExecuteReader();

if (dr.Read())
{
object o = dr.GetValue(0);
if ((o != null) && (o != DBNull.Value))
code = Convert.ToInt64(o);
else
code = 1;
}
//dr.Close();
}
catch (Exception ex)
{
Barwill.DAWeb.Utilities.ExceptionLogGateway.SaveException(ex);
}
finally
{
if (_mainConnection.State == System.Data.ConnectionState.Open)
{
if (dr != null)
if (!dr.IsClosed)
dr.Close();
_mainConnection.Close();
}
}
return code;
}

The sneaky

In order to keep performance up we often store data in Session or cache storage. if this is an inproc session state you are able to store the entire object also non serializable objects. If you hook up an event handler to a cached object in you ASP.NET pages or controls you might run into a potential memory leak. Event handlers store a reference to the subscribing object. This causes the GC to thing that your ASP.NET page is pinned and will not collect it. if your cached object lives for many hours and you have many users you will run into a OOM (Out Of Memory) exception this might be a bit anoying for the user as well as you, the developer. Below I have added a example og how to unhook your event handlers, a nice place to do this is in the OnUnload event of the page or control.

protected override void OnUnload(EventArgs e)
{
((JobDetail)Page).OnParentUpdated -= JobModuleControl_OnParentUpdated;
_CBSControl.OnControlUpdated -= ctrl_OnControlUpdated;
_CBSControl.OnStatusChanged -= ctrl_OnStatusChanged;



base.OnUnload(e);
}

No comments: