I am writing some tests to have a better understanding of how works the .NET Garbage Collector in order to build a framework without memory leak. But I am facing an unexpected behavior on my first and very simple test.
Here is my knowledge about the GC:
- It cleans up things regularly (and when it can)
- It cleans instances no longer referenced
Here is the little class I wrote to verify my knowledge:
public class People
{
private People _child;
private WeakReference<People> _parent = new WeakReference<People>(null);
public void AddChild(People child)
{
_child = child;
_child._parent.SetTarget(this);
}
}
Basically, a parent can reference its child. Based on my knowledges above, I expect that when the parent "dies", so does its child.
The little trick here is the use of a WeakReference
so the child can access to its parent but without creating a circular reference that could lead to a memory leak (that's one of the point I am trying to find out: are two instances only referencing each other garbage collected? Or in other word: do I have to use a WeakReference
in this case? My guess is that they won't be garbage collected if they reference each other directly, but I actually never checked it).
Here is the little test I wrote with xUnit
:
public class GCTests
{
[Fact]
public void TestGC()
{
var parent = new People();
var weakParent = new WeakReference(parent);
var child = new WeakReference(new People());
parent.AddChild(child.Target as People);
// Until now, there is a reference to the parent so the GC is not supposed to collect anything
parent = null;
// But now, no-one is referencing the parent so I expect the GC to collect it and removed it from memory
// Forces the GC to collect unreferenced instances
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.False(weakParent.IsAlive);
Assert.False(child.IsAlive);
}
}
The test fails on Assert.False(weakParent.IsAlive)
, meaning that someone still has a reference to the actual parent.
I also tried to use Thread.Sleep(10000);
to give the GC time to collect things but it still fail on that assert.
So my question is: why are my instances not garbage collected?
- Which one of my assertions is wrong?
- What do I mis-understand in the process of Garbage Collection or in the use of
WeakReference
?
For information, the xUnit test project I am using targets .NET Core 3
, but I hope it does not change anything in the GC process.