12

Possible Duplicate:
Resurrection difference in using Object Initializer

I am having a hard time trying to understand how garbage collector works in C# (I'm using 2012, so c# 4.5). Here is my example code:

    public class A
    {
        public int c;
        public A(){}
        public A(int pC)
        {
            c = pC;
        }
    }

    public static void Main()
    {
        // Test 1
        var a = new A {c=199};
        var aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
        //            Console.WriteLine(GC.GetGeneration(aRef.Target)); //output 1

        // Test 2
        a = new A (200);
        aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
    }

Output is True / True / True / False

It seems to me in both tests, the object on the heap has no root before calling GC.Collect. But it happens that in Test 1, the object get through the force gc run, while in Test 2 it doesn't. So, is there something mysterious going on about using initializer? My guess is that there might be "some extra code" when use initializer that would become a strong root for the same object.....

Thanks.

Community
  • 1
  • 1
alex1234231
  • 193
  • 1
  • 6

3 Answers3

5

When using the initializer say

 var a = new A {c=199}; --------> 1

compiler includes an extra reference on stack which makes object get through GC .
The statement (1) in the above turns out to be as follows

 var temp = new A() ;
  temp.c=199;
  var a=temp . 

I think this temp variable makes this object alive during GC .

Please refer this link

EDIT : As mentioned by TomTom in comments. If debugger is running then GC behaviour will be changed and variabes are kept alive until end of method, not until last use . Please correct me if I,m wrong

Community
  • 1
  • 1
Imposter
  • 2,666
  • 1
  • 21
  • 31
4

Clearly you are running either the Debug build or have a debugger attached. The garbage collector gets lifetime hints from the just-in-time compiler, it generates a table that indicates in what sections of code a local variable can be referenced. The garbage collector walks the stack of the executing method that was interrupted by the GC and checks the execution location against this table. And counts the reference as valid when it finds a match.

If the code was built in the Debug configuration or when a debugger is attached, the jitter modifies this table and lets the variable stay alive until the end of the method body. This makes it a lot easier to debug the code, you can put the local variable in a watch expression and it will produce a result even when you step past the point where the variable is no longer used.

The answer posted by @Imposter is correct, the hidden temporary variable keeps the first instance of A alive. And the garbage collector considers it valid until the end of the method because you are using the debugger. Your second a = null; assignment allows the second instance to be garbage collected.

What will really happen when you run this code in production is very different. For one, the jitter optimizer will remove the a = null assignments. It knows that those assignments have no useful side-effects so it generates no code for them. Pretty unintuitive, the best way to see this is by taking these steps:

  • Delete the a = null assignments from the code
  • Switch to the Release configuration with Build + Configuration Manager
  • Use Tools + Options, Debugging, General, untick the "Suppress JIT optimization on module load" option.

The last option change allows you to keep using the debugger without it affecting the way the jitter generates code. Now the temporary variable will no longer keep the first instance of A referenced, the table generated by the jitter will only mark it as storing a valid reference for the first statement in the method. Run your program and you'll see:

True
False
True
False

With the important new insight that setting a reference to null is not actually necessary, the garbage collector is smart enough to not require you to help.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

I Strongly believe that an extra reference on stack which makes the GC Root.

Let's confirm by SOS Debugging..

  1. When using the initializer,

      (before GC.Collect)
      01bdc110 - object address of A.
    
      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68
      ESP:20e960:Root:  01bdc110(GCTest.A)
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) -- extra stack reference generated by compiler**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)
      ESP:20ebc4:Root:  01bdc110(GCTest.A)
      Scan Thread 2404 OSTHread 964
    

    (After GC.Collect)

      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68       
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) ----//Still remains**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)         
      Scan Thread 2404 OSTHread 964
    

    Confirm that weak reference is not removed.

       !GCRoot 01bdc210 //(address of weak reference)
       Scan Thread 2920 OSTHread b68
       **ESP:20e968:Root:  01bdc210(System.WeakReference) // Created by us** 
       ESP:20ebb4:Root:  01bdc210(System.WeakReference) // other in mscorlib
       ESP:20ebc0:Root:  01bdc210(System.WeakReference) // other in mscorlib
       Scan Thread 2404 OSTHread 964
    

2, Without Initializer,

    !GCRoot 01bdd4e0 
    Scan Thread 2920 OSTHread b68
    ESP:20e960:Root:  01bdd4e0(GCTest.A)
    ESP:20eba8:Root:  01bdd4e0(GCTest.A)
    ESP:20ebc4:Root:  01bdd4e0(GCTest.A)
    Scan Thread 2404 OSTHread 964

   **No extra stack reference.**
C-va
  • 2,910
  • 4
  • 27
  • 42