2

I'm trying to write a unit test for testing a memory leak. Steps to Reproduce:

    TestClass test = new TestClass();
    WeakReference reference = new WeakReference(test, true);

    test = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    Debug.Assert(reference.Target == null, "Memory Leak"); // This works

     //Replacing the above line with following, I see "Memory Leak" being printed.

    if (reference.Target != null)
    {
        Console.WriteLine("Memory Leak");
    }

I added a finalizer:

~TestClass() { Console.WriteLine("TestClass instance finalized");}

and I noticed that the finalizer gets called as part of GC commands in the Assert case but when I replace it with if condition, the finalizer doesn't gets called as part of the GC commands and hence the reference's target is still alive. The finalizer gets called only before the program exists.

Expected Behavior:

if(reference.Target != null) Console.WriteLine("Memory Leak");

should work.

Actual Behavior:

Debug.Assert(reference.Target == null, "Memory Leak");

works but

if(reference.Target != null) Console.WriteLine("Memory Leak");

doesn't works as it prints "Memory Leak"

Rajesh Nagpal
  • 1,088
  • 1
  • 9
  • 12

1 Answers1

2

I found the root cause of this issue. This code will work as expected in the Release build configuration but not in debug(which is what I was running).

In the Debug case, the reason why "test" is not GCed because there exists a member "reference" which has a property Target that holds a reference to the "test" object. In order to be able to view this using debugger tools like Watch window, the compiler keeps it alive. If you get rid of the WeakReference instance(and corresponding if condition), you will see it being GC'ed even in Debug mode. Also, it seems that if the "reference" is used in Debug.Assert, it doesn't holds a reference to target and enables "test" to be GC'ed.

In the Release mode, the reason why "test" is GC'ed because the compilers JIT compiles the code and gets rid of the "test" variable(since it is anyways assigned to null) and there is no way to reference it anywhere in the code. That enables it to be GC'ed. Since "reference" is a weak reference to the "test" object, it will not hold it and will allow it to be GC'ed and hence the if condition works in Release mode.

Rajesh Nagpal
  • 1,088
  • 1
  • 9
  • 12