It seems some update changed GC behavior when built in Debug configuration or with debugger attached:
//Code snippet 1
var a = new object();
var w = new WeakReference(a);
a = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w.IsAlive ? "Alive" : "Dead");
Such code used to print Dead
, and it was very handy for writing unit-tests checking that certain parts that should be GCed are not being held.
After some .NET 4.x update, this code passes successfully on .NET 2.x and 3.x, but fails on all variants of 4.x. I tried to call it as GC.Collect(2, GCCollectionMode.Forced, blocking: true)
, making <gcConcurrent enabled="false"/>
in App.config
and GCSettings.LatencyMode = GCLatencyMode.Batch
- nothing helps. If I run the code without debugger attached and it is built in Release configuration (i.e. with optimizations) - it outputs Dead
. Otherwise it is Alive
.
I understand that relying on GC is not a good idea in production. But for tests I don't know how to replace ability to check through test that particular code piece does not leak memory. It is pure test assembly, I'm fine with turning some compatibility switches, or something alike. My goal is to check my own code, not the GC optimizations.
Is there a way to force GC to previous behavior somehow?
P.S. I saw almost identical question, but at that time it was related to NCrunch. I don't have it installed. I ran the code even from command line, without VS at all, with the same results.
UPD: I found that if I move code with allocating and setting reference to null into separate method - it consistently outputs Dead
, though.
//Code snippet 2
internal class Program
{
private static void Main(string[] args)
{
var w = DoWorkAndGetWeakRef();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w.IsAlive ? "Alive" : "Dead");
Console.ReadLine();
}
private static WeakReference DoWorkAndGetWeakRef()
{
var a = new object();
var w = new WeakReference(a);
a = null;
return w;
}
}
Same result if I move out to separate method GC collection calls and WeakReference check:
//Code snippet 3
internal class Program
{
private static void Main(string[] args)
{
var a = new object();
var w = new WeakReference(a);
a = null;
CollectAndCheckWeakRef(w);
Console.ReadLine();
}
private static void CollectAndCheckWeakRef(WeakReference w1)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w1.IsAlive ? "Alive" : "Dead");
}
}
The important point though as it seems is that original w
variable is not in current scope. If I move Console.WriteLine(w1.IsAlive ? "Alive" : "Dead");
back to Main
- it becomes Alive
again.
Both variants are not very convenient sometimes, but at least consistent (Debug
or Release
configuration, debugger is attached or not - still outputs Dead
).
Now I'm curious how mere presence of WeakReference variable in current execution scope prevents GC from cleaning its Target and why having it somewhere in scope buried in call stack doesn't do the same.