The Garbage Collection Algorithm The garbage collector checks to see whether there are any objects in the heap that are no longer being used by the application. If such objects exist, the memory used by these objects can be reclaimed. (If no more memory is available in the heap, then new throws an OutOfMemoryException exception.) How does the garbage collector know whether or not the application is using an object? As you might imagine, this isn’t a simple question to answer. Every application has a set of roots. A single root is a storage location containing a memory pointer to a reference type. This pointer either refers to an object in the managed heap or is set to null. For example, all global or static reference type variables are considered roots. In addition, any reference type local variable or parameter variable on a thread’s stack is also considered a root. Finally, within a method, a CPU register that refers to a reference type object is also considered a root. When the JIT compiler compiles a method’s IL, in addition to producing the native CPU code, the JIT compiler also creates an internal table. Logically, each entry in the table indicates a range of byte offsets in the method’s native CPU instructions, and for each range, a set of memory addresses (or CPU registers) that contain roots. For example, the table could logically look like Table 19-1. Table 19-1: Sample of a JIT CompilerÐProduced Table Showing Mapping of Native Code Offsets to a Method’s Roots Starting Byte Offset
Ending Byte Offset
Roots
0x00000000
0x00000020
this, arg1, arg2, ECX, EDX
0x00000021
0x00000122
this, arg2, fs, EBX
0x00000123
0x00000145
fs
If a garbage collection were to start while code was executing between offset 0x00000021 and 0x00000122 in the method, then the garbage collector would know that the objects referred to by the this parameter, arg2 parameter, fs local variable, and the EBX register were all roots and refer to objects in the heap that shouldn’t be considered garbage. In addition, the garbage collector can walk up the thread’s call stack and determine the roots for all the calling methods by examining each method’s internal table. The garbage collector uses other means to obtain the set of roots stored in global and static reference type variables. Note In Table 19-1, notice that the method’s arg1 argument isn’t referred to after the CPU instruction at offset 0x00000020. This means that the object arg1 refers to can be collected anytime after this instruction executes (assuming that there are no other roots in the application that also refer to this object). In other words, as soon as an object becomes unreachable, it is a candidate for collection—objects aren’t guaranteed to live throughout a method’s lifetime. However, when an application is running under a debugger or when an assembly contains the System.Diagnostics.DebuggableAttribute attribute with its constructor’s isJITOptimizerDisabled parameter set to true, the JIT compiler extends the life of all variables (value type and reference type) until the end of their scope, which is usually the end of the