Coder Perfect

In.NET, garbage collection is something that you should be familiar with.


Take the following code into consideration:

public class Class1
    public static int c;

public class Class2
    public static void Main()
            var c1=new Class1();
            //c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
        Console.WriteLine(Class1.c); // prints 0

Why isn’t the variable c1 in the main method finalized even though it’s out of scope and not referenced by any other object when GC.Collect() is called?

Asked by Victor Mukherjee

Solution #1

Because you’re using a debugger, you’re being tripped up and making erroneous conclusions. You’ll need to test your code on the same system as your user. Change the “Active solution configuration” combo in the upper left corner to “Release” and then switch to the Release build with Build + Configuration manager. Uncheck the “Suppress JIT optimization” option in Tools + Options, Debugging, General.

Run your application once more and fiddle with the source code. Take note of how the extra braces have no effect. It’s also worth noting that changing the variable to null has no effect. It will always print the number one. It now performs as you had hoped and anticipated.

Which leaves you with the problem of explaining why the Debug build behaves so differently. This necessitates a discussion of how the garbage collector detects local variables and how this is modified by the presence of a debugger.

To begin, when the jitter converts the IL for a method into machine code, it performs two key tasks. The first is highly visible in the debugger; using the Debug + Windows + Disassembly window, you can examine the machine code. The second responsibility, on the other hand, is utterly unnoticed. It also provides a table that explains how the method body’s local variables are used. Each method parameter and local variable has a two-address entry in that table. The variable’s initial storage address for an object reference. And the address of the machine code instruction that no longer uses that variable. Also, if the variable is saved in a cpu register or on the stack frame.

When the garbage collector runs a collection, it has to know where to look for object references, therefore this table is necessary. When the reference is part of an object on the GC heap, it’s rather simple to perform. When the object reference is kept in a CPU register, it’s not easy to perform. The table directs you to the appropriate location.

The table’s “no longer utilized” address is quite crucial. It increases the efficiency of the waste collector. It can gather an object reference even if it’s utilized within a method that hasn’t finished running yet. Your Main() method, for example, will only ever finish executing shortly before your program terminates, which is extremely common. Obviously, you don’t want any object references used inside the Main() method to persist throughout the program; that would be a leak. Depending on how far the program has advanced inside that Main() method before it made a call, the jitter can utilize the table to find that such a local variable is no longer usable.

GC is a near-magical mechanism that is related to that table. KeepAlive(). It’s a unique function because it doesn’t generate any code at all. Its sole purpose is to change that table. It increases the lifetime of the local variable, preventing trash collection of the reference it holds. The only reason you’ll need it is to prevent the GC from collecting a reference too quickly, which can happen in interop circumstances where a reference is handed to unmanaged code. Because such code was not compiled by the jitter, the garbage collector cannot see such references because it lacks the table that tells it where to look for the reference. A delegate object is sent to an unmanaged function.

As you can see from your sample snippet after running it in the Release build, local variables can be collected before the method completes. Even more powerful, an object can be collected while one of its methods is being executed if that method no longer references to it. There is an issue with this; debugging such a procedure is quite inconvenient. Because you have the option of putting the variable in the Watch window or inspecting it. And if a GC occurs while you’re debugging, it’ll vanish. When would be extremely inconvenient, therefore the jitter is aware that a debugger is attached. The table is then modified, and the “last used” address is changed. And it’s changed from its original value to

This clarifies what you saw before and why you posed the question. Because of the GC, it prints “0.” Collect call is unable to obtain the reference. According to the table, the variable is still in use after the GC. All the way to the end of the procedure, there is a call to Collect(). By using the debugger and running the Debug build, I was forced to say so.

Because the GC inspects the variable and no longer sees a reference, setting the variable to null has an impact now. But don’t fall into the trap that many C# programmers have fallen into: developing that code was worthless in the first place. When you run the code in the Release build, it makes no difference whether or not that statement is there. The jitter optimizer will actually eliminate that statement because it has no effect. So, despite the fact that it appeared to work, don’t create code like that.

Finally, this is what gets programmers into problems when they develop little programs to perform things with an Office app. They want the Office software to exit on demand, and the debugger frequently leads them down the wrong path. Calling GC.Collect is the proper way to go about it (). When they debug their program, though, they’ll realize that it doesn’t work, driving them into never-never land by invoking Marshal. ReleaseComObject(). Manual memory management rarely works because people are prone to overlooking an interface reference that isn’t apparent. GC.Collect() does work, but not while debugging the app.

Answered by Hans Passant

Solution #2

[I just wanted to give a few more details about the Finalization Process Internals]

When an object is created, the object’s Finalize function should be invoked when the item is trash collected. However, finalization entails more than this simple assumption.


Assume that classes/objects A, B, D, G, and H do not implement the Finalize method, but classes/objects C, E, F, I, and J do.

The new operator allocates memory from the heap when an application creates a new object. A pointer to the object is placed on the finalisation queue if the object’s type has a Finalize method. As a result, the finalisation queue receives pointers to objects C, E, F, I, and J.

The garbage collector manages the finalization queue, which is an internal data structure. Before memory can be reclaimed, each entry in the queue leads to an object that should have its Finalize function invoked.

A heap containing numerous things is seen in the diagram below. Some of these items can be accessed from the application’s root, while others cannot. The.NET framework recognizes that objects C, E, F, I, and J have Finalize methods when they are created, and references to these objects are placed to the finalization queue.

Objects B, E, G, H, I, and J are considered to be garbage when a GC happens (1st Collection). The application code displayed as arrows from the yellow box above can still reach A,C,D, and F.

The garbage collector looks for pointers to these items in the finalization queue. When a pointer is found, it is moved from the finalization queue to the freachable queue (“F-reachable”, i.e. finalizer reachable). Another internal data structure managed by the garbage collector is the freachable queue. Each pointer in the freachable queue denotes an object whose Finalize method is ready to be called.

After the first GC, the managed heap should resemble the image below. The following is an explanation:

Calling Finalize methods is handled by a separate runtime thread. This thread sleeps when the freachable queue is empty (which is frequently the case). When entries come, however, this thread awakens, removes each entry from the queue, and runs the Finalize function on each object. The garbage collector compacts reclaimable memory, and a dedicated runtime thread empties the freachable queue by calling the Finalize method on each object. So this is the point at which your Finalize method is called.

Because the application’s roots no longer point to it and the freachable queue no longer points to it (it’s also EMPTY), the garbage collector sees that the finalized objects are truly garbage the next time it is invoked (2nd GC), and the memory for the objects E, I, and J can be reclaimed from the heap. Examine the diagram below and compare it to the diagram above.

The crucial point to remember here is that to reclaim memory consumed by objects that need to be finalized, two GCs are required. In practice, multiple collections may be required, as these items may be promoted to an older generation.

NOTE: The freachable queue is a root in the same way that global and static variables are. As a result, if an object is on the reachable queue, it is reachable rather than garbage.

Finally, keep in mind that although debugging applications is one thing, trash collection is quite another. Until now, debugging programs hasn’t been able to detect garbage collection. If you want to learn more about memory, start here.

Answered by R.C

Post is based on