bgh
bgh

Reputation: 2120

Force target of WeakReference to be garbage collected

In my program I have a service object which keeps weak references to objects. I have an automated test that asserts that when all references to the object are removed, the weak reference becomes freed up. The purpose of the test is to prove that the service won't cause objects to remain in memory when there is no reason for them to be kept around.

Here is a simplified version of that test:

[Test]
public void WeakReference()
{
    var o = Allocate();
    var reference = new WeakReference(o.Value);
    Assert.IsTrue(reference.IsAlive);

    o.Value = null;
    GC.Collect();
    Assert.IsFalse(reference.IsAlive);
}

private MyObject Allocate()
{
    return new MyObject
    {
        Value = new object()
    };
}

private class MyObject
{
    public object Value { get; set; }
}

In a .NET 4.8 NUnit test project this test passes. However, on the current branch of the project, I have the test inside of a .NET 5 project. In that project, this same test code fails because the weak reference remains "alive".

I also tried by changing the GC.Collect() call to

GC.Collect(int.MaxValue, GCCollectionMode.Forced, blocking: true);

No dice.

Has there been a fundamental change in the way the garbage collection works in .NET 5 that makes the above code no longer work? How can I still prove that my service object is not holding onto objects in memory?

Upvotes: 2

Views: 560

Answers (1)

Ronnie Overby
Ronnie Overby

Reputation: 46470

I was able to cause your test to pass under these 3 conditions:

  1. Loop over GC.Collect() while IsAlive.
  2. Compiler optimizations are ON.
  3. The debugger is not attached.

This modified test code demonstrates the looping mentioned in #1 above and repeats the test a significant number of times. It always passes for me.

[Test, Repeat(1000)]
public void WeakReference()
{
    var o = Allocate();
    var reference = new WeakReference(o.Value);
    Assert.IsTrue(reference.IsAlive);

    o.Value = null;

    for (var i = 0; i < 2 && reference.IsAlive; i++)
        GC.Collect();

    Assert.IsFalse(reference.IsAlive);
}

I'm not yet knowledgeable enough to say why this works exactly, but I've gathered that it has to do with the conditions that will cause an object reference to be a GC root (always reachable & not collectable by GC).

Upvotes: 2

Related Questions