In my post, I discussed the as an aggressive behavior of JIT (in Release mode / optimized code) to assist the garbage collector (GC), so that a object is not considered to be a root beyond the point of its usage. previous eager root collection But sometimes we may want to extend the lifetime of an object beyond its usual lifetime. In this post, we will see an example of a scenario where we may want to extend the lifetime of an object. Also, we will look at how we can accomplish this in two ways — one is writing an API ourselves (in the spirit of learning by doing) and other is to use existing API in .NET. Consider the below code to display the current time using a timer endlessly till we decide to exit the program: System; System.Runtime.CompilerServices; { { timer = Timer((state) => Console.WriteLine( ), , , ); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.ReadLine(); } } using using class Program ( ) static void Main [] args string var new $"Time is " {DateTime.Now.ToShortTimeString()} null 0 200 Try running this under mode, you will see an output as below. I hope you are not surprised by this result. You can see the output prints the time endlessly until we exit the program. Debug Time : PM Time : PM Time : PM Time : PM Time : PM Time : PM ……. is 7 03 is 7 03 is 7 03 is 7 03 is 7 03 is 7 03 Now try running the same code under mode. Release The output when we run the same code under Release mode is . The does not fire at all (or may only fire once or twice depending on the timing between execution garbage collection and firing). nothing timer timer Surprised! (or maybe not if you know about or have already read my on ) eager root collection previous blog eager root collection This happens because of . The object is not being used after line 8, hence when we initiate the garbage collection from line 9 to line 11, object is not reported as by JIT, hence garbage collector does not mark it during and gets collected by it during . eager root collection timer timer live roots mark phase sweep phase So, we have a situation where our code works as expected (timer fires endlessly till program exit) in mode, but does not work (timer does not fire) at all in the mode. Debug Release This is the quintessential moment! “it works on my machine” The obvious question is what can I do about it? How can I ensure my code works as expected in both the Debug mode and Release mode? We like the idea of surprises (I know my wife does!), but we don’t like the unpleasant ones and most definitely don’t like it in production systems. The is to add just a single line of code in our method using the API in .NET that will give our humble object the much needed lifeline, so it can live longer and keep firing to show us the time till we want (but within the lifetime of the method execution). But before I come to that, let us try and write my own version of it - armed with our knowledge of and . solution Main() timer eager root collection method inlining We will design our API as a static class called with a static method called . Lifeline KeepAlive() Here is the full source code of my Lifeline API. System.Runtime.CompilerServices; { [ ] { } } using public static class Lifeline MethodImpl(MethodImplOptions.NoInlining) ( ) public static void KeepAlive obj object Yes, you seeing it right. The method has absolutely no code in it. We are not using the argument we are being sent by the caller of this method. The key feature of this method is the attribute on the method Lifeline.KeepAlive() obj [MethodImpl(MethodImplOptions.NoInlining)] . This guarantees the JIT will never try to inline this method. What this means is, the call to this method will never be ‘inlined’, and a call to this method will definitely be made by passing the argument to its method parameter of type object (the design choice to have object type in the parameter is deliberate, so that this method can be invoked for any type of object). Now we will invoke the API method from the method of our code as below and run it under Release mode (as well as Debug mode). Lifeline.KeepAlive() Main() Please note the call to at line 14 by passing object as the argument : Lifetime.KeepAlive() timer System; System.Runtime.CompilerServices; { { timer = Timer((state) => Console.WriteLine( ), , , ); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.ReadLine(); Lifeline.KeepAlive(timer); } } { [ ] { } } using using class Program ( ) static void Main [] args string var new $"Time is " {DateTime.Now.ToShortTimeString()} null 0 200 // This will extend the lifetime of the timer object till this line of code public static class Lifeline MethodImpl(MethodImplOptions.NoInlining) ( ) public static void KeepAlive obj object Hurray! We could extend the lifetime of an object with a single small dose of . Lifeline.KeepAlive() Time : PM Time : PM Time : PM Time : PM Time : PM Time : PM ……. is 7 45 is 7 45 is 7 45 is 7 45 is 7 45 is 7 45 As promised, let us now discuss how we can accomplish this using existing .NET API. You can save yourself the effort of writing a API as I did above and simply call provided in .NET. is a static method on .NET framework’s (and .NET Core) class. Lifeline GC.KeepAlive() KeepAlive() GC You might imagine must implement a very complicated and sophisticated logic — after all it is extending the lifetime of an object and messing with the default Release mode JIT behavior. But you may surprised to note, the implementation of is exactly identical to we wrote — does not do anything, its method is completely empty and does nothing with the object we pass to it. GC.KeepAlive() GC.KeepAlive() Lifeline.KeepAlive() GC.KeepAlive() It uses the power of to prevent JIT from inlining it I encourage you to take a look at the implementation of in . [MethodImpl(MethodImplOptions.NoInlining)] . GC.KeepAlive() .NET framework source code We can change our code to use the .NET API to extend the lifetime of any object till the point of invocation of . Please note the call to the at line 14: GC.KeepAlive() GC.KeepAlive() GC .KeepAlive() using System; Program { static void { var timer = => Console. , null, , ); GC. ; GC. ; GC. ; Console. ; GC. ; } } class Main( [] ) string args new Timer(( ) state WriteLine($ ) "Time is {DateTime.Now.ToShortTimeString()}" 0 200 Collect() WaitForPendingFinalizers() Collect() ReadLine() KeepAlive( ) timer // This will extend the lifetime of timer object till this line of code The lifetime of the object has been extended till the point of invocation of on line 14. timer GC.KeepAlive() Digging Deeper — for the more curious souls We can use the tool (in conjunction with extension) to further examine and verify the JIT behavior and GC roots in Release mode with and without WinDbg SOS GC.KeepAlive(). If we run the code in mode, but without the , and examine the (command on line 11), we will observe that there are no stack roots and also there are no instances of Timer in the heap (indicated by no entry when we execute command on line 43). Release GC.KeepAlive() GCInfo !gcinfo System.Threading.Timer !dumpheap : > .load C:\Users\anand\.dotnet\sos\sos.dll : > !name2ee MyPlaygroundCore MyPlaygroundCore.Program.Main Module: f9d41af820 Assembly: MyPlaygroundCore.dll Token: MethodDesc: f9d41b1b50 Name: MyPlaygroundCore.Program.Main(System.String[]) JITTED Code Address: f9d4110e90 : > !gcinfo f9d4110e90 entry point FF9D4110E90 Normal JIT generated code GC info FF9D41E4978 Pointer table: Prolog size: Security object: <none> GS cookie: <none> PSPSym: <none> Generics inst context: <none> PSP slot: <none> GenericInst slot: <none> Varargs: Frame pointer: rbp Wants Report Only Leaf: Size of parameter area: Return Kind: Scalar Code size: ee a safepoint: a a safepoint: bb a safepoint: ba +rdi dc a safepoint: a safepoint: a safepoint: d a safepoint: a safepoint: b0 a safepoint: c3 a safepoint: d0 a safepoint: ed a safepoint: : > !dumpheap -type System.Threading.Timer Address MT Size ad1419b0b8 f9d41e2e58 ad1419b2a8 f9d41e5760 ad1419b300 f9d41e56f0 ad1419b368 f9d41e56f0 ad1419b3b8 f9d41e56f0 ad1419b408 f9d41e56f0 ad1419b458 f9d41e56f0 ad1419b4a8 f9d41e56f0 ad1419b4f8 f9d41e56f0 ad1419b548 f9d41e56f0 ad1419b8f0 f9d41e8280 Statistics: MT Count TotalSize Class Name f9d41e8280 System.Threading.TimerQueue+AppDomainTimerSafeHandle f9d41e2e58 System.Threading.TimerCallback f9d41e5760 System.Threading.TimerQueue[] f9d41e56f0 System.Threading.TimerQueue Total objects 0 000 0 000 00007f 0000000006000001 00007f 00007f 0 000 00007f 00007 00007 0 0 0 0 1 00000049 is 0000006 is 000000 is 000000 000000 is 00000114 is 00000127 is 0000015 is 00000170 is 000001 is 000001 is 000001 is 000001 is 0 007 000002 00007f 64 000002 00007f 88 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 80 000002 00007f 32 00007f 1 32 00007f 1 64 00007f 1 88 00007f 8 640 11 As discussed, when we add GC.KeepAlive(), the timer object will be kept alive. To verify this let us examine using WinDbg (and SOS.dll). We can observe there is a live root in stack at stack position as indicated by the result of on line 11. rbp-40 !gcinfo This can be further confirmed by command on line 53 that shows the presence of 1 object in the heap of type r (line 75)and by command (line 83) that conclusively shows stack location points to an object of type (line 86 and line 87). !dumpheap System.Threading.Time !gcroot rb-40 System.Threading.Timer : &gt; .load C:\Users\anand\.dotnet\sos\sos.dll : &gt; !name2ee MyPlaygroundCore MyPlaygroundCore.Program.Main Module: f9d419f820 Assembly: MyPlaygroundCore.dll Token: MethodDesc: f9d41a1b50 Name: MyPlaygroundCore.Program.Main(System.String[]) JITTED Code Address: f9d4100e90 : &gt; !gcinfo f9d4100e90 entry point FF9D4100E90 Normal JIT generated code GC info FF9D41D4990 Pointer table: Prolog size: Security object: &lt;none&gt; GS cookie: &lt;none&gt; PSPSym: &lt;none&gt; Generics inst context: &lt;none&gt; PSP slot: &lt;none&gt; GenericInst slot: &lt;none&gt; Varargs: Frame pointer: rbp Wants Report Only Leaf: Size of parameter area: Return Kind: Scalar Code size: c a safepoint: d a safepoint: be a safepoint: bd +rdi a safepoint: +rbp e a safepoint: d +rbp a safepoint: +rbp a safepoint: +rbp a a safepoint: +rbp ba a safepoint: b9 +rbp cd a safepoint: cc +rbp da a safepoint: d9 +rbp a safepoint: a safepoint: : &gt; !dumpheap -type System.Threading.Timer Address MT Size a4b0b8 f9d41c2e58 a4b0f8 f9d41c3138 a4b110 f9d41c47f0 a4b2a8 f9d41c5760 a4b300 f9d41c56f0 a4b368 f9d41c56f0 a4b3b8 f9d41c56f0 a4b408 f9d41c56f0 a4b458 f9d41c56f0 a4b4a8 f9d41c56f0 a4b4f8 f9d41c56f0 a4b548 f9d41c56f0 a4b8f0 f9d41c8280 a4b938 f9d41c4968 a4b9c0 f9d41c86a8 Statistics: MT Count TotalSize Class Name f9d41c86a8 System.Threading.TimerQueueTimer+&lt;&gt;c f9d41c4968 System.Threading.TimerHolder f9d41c3138 System.Threading.Timer f9d41c8280 System.Threading.TimerQueue+AppDomainTimerSafeHandle f9d41c2e58 System.Threading.TimerCallback f9d41c5760 System.Threading.TimerQueue[] f9d41c47f0 System.Threading.TimerQueueTimer f9d41c56f0 System.Threading.TimerQueue Total objects : &gt; !gcroot a4b0f8 Thread 8: B161FE7B0 FF9D410106A MyPlaygroundCore.Program.Main(System.String[]) rbp : b161fe830 -&gt; A4B0F8 System.Threading.Timer Found unique roots (run to see all roots). 0 000 0 000 00007f 0000000006000001 00007f 00007f 0 000 00007f 00007 00007 0 0 0 0 201 0000004 is 0000006 is 000000 is 000000 000000e6 is 000000e5 -40 0000011 is 0000011 -40 00000131 is 00000130 -40 00000167 is 00000166 -40 0000017 is 00000179 -40 000001 is 000001 -40 000001 is 000001 -40 000001 is 000001 -40 000001e3 is 00000200 is 0 000 0000024306 00007f 64 0000024306 00007f 24 0000024306 00007f 96 0000024306 00007f 88 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 80 0000024306 00007f 32 0000024306 00007f 24 0000024306 00007f 24 00007f 1 24 00007f 1 24 00007f 1 24 00007f 1 32 00007f 1 64 00007f 1 88 00007f 1 96 00007f 8 640 15 0 000 0000024306 52f 0000005 00007 -40 0000005 0000024306 1 '!gcroot -all' Conclusion The JIT has an aggressive behavior when we run our code in Release mode (optimized code). This leads to what is known as Objects may get collected by even though they may still appear to exist from lexical point of view. eager root collection. GC But if we want, we can extend the lifetime of objects. This can be done via API of .NET. We also discussed how we can write our own implementation of this API to better understand and appreciate how achieves its objective of extending an object’s lifetime in a surprisingly trivial manner. GC.KeepAlive() GC.KeepAlive() Previously published at https://medium.com/swlh/bless-your-net-object-with-a-shot-of-lifeline-d04393d240fb