Garbage Collection should have removed object but WeakReference.IsAlive still returning true
Solution 1:
Hit the same issue as you - my test was passing everywhere, except for under NCrunch (could be any other instrumentation in your case). Hm. Debugging with SOS revealed additional roots held on a call stack of a test method. My guess is that they were a result of code instrumentation that disabled any compiler optimizations, including those that correctly compute object reachability.
The cure here is quite simple - don't ever hold strong references from a method that does GC and tests for aliveness. This can be easily achieved with a trivial helper method. The change below made your test case pass with NCrunch, where it was originally failing.
[TestMethod]
public void WeakReferenceTest2()
{
var wRef2 = CallInItsOwnScope(() =>
{
var obj = new object();
var wRef = new WeakReference(obj);
wRef.IsAlive.Should().BeTrue(); //passes
GC.Collect();
wRef.IsAlive.Should().BeTrue(); //passes
return wRef;
});
GC.Collect();
wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes
}
private T CallInItsOwnScope<T>(Func<T> getter)
{
return getter();
}
Solution 2:
There are a few potential issues I can see:
I am unaware of anything in the C# specification which requires that the lifetimes of local variables be limited. In a non-debug build, I think the compiler would be free to omit the last assignment to
obj
(setting it tonull
) since no code path would cause the value ofobj
will never be used after it, but I would expect that in a non-debug build the metadata would indicate that the variable is never used after the creation of the weak reference. In a debug build, the variable should exist throughout the function scope, but theobj = null;
statement should actually clear it. Nonetheless, I'm not certain that the C# spec promises that the compiler won't omit the last statement and yet still keep the variable around.If you are using a concurrent garbage collector, it would may be that
GC.Collect()
triggers the immediate start of a collection, but that the collection wouldn't actually be completed beforeGC.Collect()
returns. In this scenario, it may not be necessary to wait for all finalizers to run, and thusGC.WaitForPendingFinalizers()
may be overkill, but it would probably solve the problem.When using the standard garbage collector, I would not expect the existence of a weak reference to an object to prolong the existence of the object in the way that a finalizer would, but when using a concurrent garbage collector, it's possible that abandoned objects to which a weak reference exists get moved to a queue of objects with weak references that need to be cleaned up, and that the processing of such cleanup happens on a separate thread that runs concurrently with everything else. In such case, a call to
GC.WaitForPendingFinalizers()
would be necessary to achieve the desired behavior.
Note that one should generally not expect that weak references will be invalidated with any particular degree of timeliness, nor should one expect that fetching Target
after IsAlive
reports true will yield a non-null reference. One should use IsAlive
only in cases where one wouldn't care about the target if it's still alive, but would be interested in knowing that the reference has died. For example, if one has a collection of WeakReference
objects, one may wish to periodically iterate through the list and remove WeakReference
objects whose target has died. One should be prepared for the possibility that WeakReferences
might remain in the collection longer than would be ideally necessary; the only consequence if they do so should be a slight waste of memory and CPU time.
Solution 3:
As far as I know, calling Collect
does not guarantee that all resources are released. You are merely making a suggestion to the garbage collector.
You could try to force it to block until all objects are released by doing this:
GC.Collect(2, GCCollectionMode.Forced, true);
I expect that this might not work absolutely 100% of the time. In general, I would avoid writing any code that depends on observing the garbage collector, it is not really designed to be used in this way.
Solution 4:
Could it be that the .Should()
extension method is somehow hanging on to a reference? Or perhaps some other aspect of the test framework is causing this issue.
(I'm posting this as an answer otherwise I can't easily post the code!)
I have tried the following code, and it works as expected (Visual Studio 2012, .Net 4 build, debug and release, 32 bit and 64 bit, running on Windows 7, quad core processor):
using System;
namespace Demo
{
internal class Program
{
private static void Main(string[] args)
{
var obj = new object();
var wRef = new WeakReference(obj);
GC.Collect();
obj = null;
GC.Collect();
Console.WriteLine(wRef.IsAlive); // Prints false.
Console.ReadKey();
}
}
}
What happens when you try this code?