Master Memory Management
Write faster VB.NET and C# apps by leveraging how the .NET Framework manages memory—and by avoiding common programming mistakes.
by Francesco Balena
December 2002 Issue
Technology Toolbox: VB.NET, C#
The .NET Framework changed the rules for managing memory. Whether you come from VB6 or C++, you should learn how your apps can take advantage of these new inner mechanisms rather than work against them. Knowing the details of .NET memory management can make the difference between a sluggish application and a super-fast one; I'll show you several techniques that can help you write optimized code.
For starters, I'll review quickly how the .NET memory manager works. When you create a new object, the .NET Framework allocates memory from the so-called managed heap. Memory in this heap is allocated in a linear fashion: The Common Language Runtime (CLR) manages an internal pointer that points at the address in memory where the next object will be allocated. Allocating memory for an object is as simple as advancing this pointer past the memory used by the object, a process that's remarkably faster than memory allocation under VB6 and COM. (The CLR knows exactly how many bytes each object requires because this information is stored in the class's metadata.) Also, .NET objects don't use an internal reference counter (as VB6/COM objects do), so setting and clearing an object variable doesn't require an AddRef or Release method call. (These hidden calls add overhead to all Set commands in a VB6 app.)
The size of the managed heap isn't infinite, so a New operation would eventually make the pointer advance past the end of the managed heap, and a garbage collection would fire. The purpose of the garbage collection (GC) is to release the memory allocated to objects that are no longer in use. The lack of a reference counter means the CLR must inspect all the variables in the client application and mark the objects they're pointing to as used; these variables are also known as roots. The process must be recursive because an object can point to other objects. The process ends only when all the objects that are reachable from the application have been marked—either directly through a root or indirectly through an intermediate object. By the way, the CLR uses metadata again to determine which objects a given object points to, so you can see how central metadata and reflection are in the .NET Framework.
As you might imagine, the garbage-collection process is slow—at least in terms of CPU cycles—and the CLR must freeze all the application's threads while a GC is running, except the thread on which the GC runs. Microsoft has done its best to make this process as fast as possible by using all the tricks it could devise. For example, the CLR can retrieve the list of object roots quickly by means of a table the Just In Time (JIT) compiler creates when it compiles the Intermediate Language (IL) into native code. Remember that the JIT compiler might decide to store an object root in a CPU register rather than in a memory location for optimization purposes, and the list of object roots must account for this decision.
Back to top
|