This post discusses the aggressive and hungry nature of the garbage collection process in .NET as well as a concept that is often referred to as eager root collection . I deliberately use the phrase and not . This is because the behavior we are going to discuss in this post is actually the work of JIT and the garbage collector. This statement is quite important as it goes against the popular notion about the role of garbage collector; however this JIT behavior does contribute to the garbage collection process by assisting the garbage collector as we will see in this post. garbage collection process garbage collector not Consider the below code: System; System.Runtime.CompilerServices; { { Tiger tiger = Tiger(); tiger.Run(); age = tiger.GetAge( ); Console.WriteLine( ); Console.ReadLine(); } } { YearOfBirth { { ; } } ~Tiger() { Console.WriteLine( ); } { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.WriteLine( ); } { currentYear - YearOfBirth; } } using using class Program ( ) static void Main [] args string new var 2020 $"Tiger's age is " {age} public class Tiger public int get return 2010 "Tiger Dead" ( ) public void Run "Tiger is running" ( ) public int GetAge currentYear int return Can you guess the what the output will be ? Well, the answer is — it depends! If we run this under mode (non-optimized code), you will see the output as below: Debug Tiger is running Tiger’s age is 10 If we run this under mode (optimized code), you will see the output as below: Release Tiger Dead Tiger is running Tiger’s age is 10 Surprised! How can tiger be running after being dead! This is where the comes in. The JIT is a very smart compiler that compiles MSIL code to native assembly code that is executed by the processor. JIT is aware exactly what objects are at every line of code. It uses this knowledge to aggressively identify objects that are no longer in use to inform the garbage collector which objects in a method is eligible for garbage collection. It maintains the list of in the stack and registers in the form of and makes it available to the GC when it needs this during garbage collection. JIT optimization “live” live roots GCInfo Now, how can we relate and reconcile what I just described above about JIT with our code. At line 8, the JIT identifies the object to be no longer When garbage collector is invoked inside method from line 26 to line 28, object is collected by GC, since JIT informed the GC that is not a live root. As part of the garbage collection, the destructor of the instance is called (by ) and you see the output before proceeding to executing the rest of the program. tiger live. Run() tiger tiger Tiger finalizer thread ‘Tiger is dead’ Now, the question is why does JIT think that object is not a object at line 8, when in fact we are executing a method of object itself. Not only that, we using the object on line 9 again to call method, after the execution of method. How can we call a method of a object if it has already been collected by the GC and its destructor has been called already. tiger live tiger tiger tiger.GetAge() Run() In summary, from the lexical scope of point of view, object is being used at line 8 and line 9, and yet JIT believes it is not being used. How can that be? Remember I said JIT is a smart (and very aggressive) compiler? JIT knows that when it is executing the method, it is not using the object in any way. The statements inside method can be run without the need to have the actual reference to the object. So, it does not identify as . tiger Run() tiger Run tiger tiger live root But wait! That only explains we are not using the object inside the method, but what about line 9? Are we not using object to invoke the method? Not just that, the method also uses the instance property during its execution, so it needs the object to execute . Then how can JIT make a decision that object is not a inside method? This is the magic of of methods by JIT. Remember, I said the JIT is a smart beast? Based on some inlining rules (used to determine if inlining a method will give any benefits or not), the JIT may decide to inline certain methods. The exact rules of method inlining is beyond the scope of this post, but you may refer to if you are curious. Inlining means instead of making a method call, the JIT will execute the statements of the target method in the calling method at the point of method invocation. It may also do some further optimizations (like replacing with hardcoded integer value 2010) to make this possible. In the case of method invocation, the JIT decides to inline it, which means instead of : tiger Run() tiger GetAge() GetAge() tiger YearOfBirth tiger GetAge() tiger live root Run() inlining this post “inline” YearOfBirth GetMethod() var age = tiger.GetAge(2010) the JIT emits code equivalent to var age = 10 // this is arithmetic difference of 2020 an 2010 and the above statement obviously does not require the usage of object. tiger Hence, JIT can safely identify the object as and report to garbage collector accordingly. It does this by maintaining something called for every JIT compiled method. The of a method informs the garbage collector what objects are to be considered as . GC uses this information during its phase of garbage collection. The objects not marked are deemed to be “ and will be collected by the garbage collector in its phase tiger not live GCInfo GCInfo live root mark garbage” sweep . If you are curious, below is the JIT optimized code for method in the mode. Notice line 14 where a hardcoded value of is being used. ( is the hex equivalent of number ). There is no call to method. In the mode JIT code (not shown here for brevity), you will see an assembly statement like , similar to as you can see below : Main() Release 0xa 0xa 10 Tiger.GetAge() Debug call Tiger.GetAge(Int32) call Tiger.Run() Program.Main(System.String[]) , , [ + ], [ + ], [ + ], [ + ], , , Tiger.Run() , [ + ], , , , [ ] , , [ ] , [ + ] [ ], [ + ], [ + ], [ + ], , [ + ] , , System.String.FormatHelper(System.IFormatProvider, System.String, System.ParamsArray) , System.Console.WriteLine(System.String) System.Console.ReadLine() , L0000: sub rsp 0x48 L0004: xor eax eax L0006: mov rsp 0x28 rax L000b: mov rsp 0x30 rax L0010: mov rsp 0x38 rax L0015: mov rsp 0x40 rax L001a: mov rcx 0x7ff965bed120 L0024: call 0x00007ff9bc52d7a0 L0029: mov rcx rax L002c: call L0031: mov rcx 0x7ff95cabb1e8 L003b: call 0x00007ff9bc6178f0 L0040: mov dword ptr rax 8 0xa L0047: xor r8d r8d L004a: mov rdx 0x1d3d8f31388 L0054: mov rdx rdx L0057: mov rcx 0x1d3d9587158 L0061: mov rcx rcx L0064: lea r9 rsp 0x28 L0069: mov r9 rax L006c: mov r9 8 r8 L0070: mov r9 0x10 r8 L0074: mov r9 0x18 rdx L0078: lea r8 rsp 0x28 L007d: mov rdx rcx L0080: xor ecx ecx L0082: call L0087: mov rcx rax L008a: call L008f: call L0094: nop L0095: add rsp 0x48 L0099: ret Conclusion The garbage collection behavior can be different between code ( build) and code ( build). The difference can be due to of objects in case of optimized code. This happens due to the smart and aggressive nature of compiler in the optimized build. It may also use the magic of a method if it deems appropriate and any other code optimizations it sees fit during the compilation of code from MSIL to assembly instructions. JIT maintains that informs the GC about the in a method for any given line of statement in the method. GC uses this information to perform its phase using the and considers other unmarked objects as garbage and proceeds to collect those in the phase. optimized Release unoptimized Debug eager collection JIT inling GCInfo live roots mark live roots sweep In the next post, we discuss how to extend the lifetime of an object.