My .NET Core 2.1 application, running on Windows x64, allocates large arrays of structs, each struct holding a class in a field. The arrays end up on the Large Object Heap, so they are not collected until a full (gen 2) GC. That was expected. What surprised me a bit is that the classes referenced by the structs also don't seem to get GCed until a gen 2 GC. Is this expected behaviour?
Something like this happens if I have a large HashSet as well, because internally a HashSet keeps an array of HashSet.Slot - a struct.
Some code that reproduces the problem:
class MyClass
{
public long Value;
~MyClass()
{
if (Value < 10)
{
Console.WriteLine("MyClass finalizer " + Value);
}
}
}
struct TheEntry
{
public MyClass TheClass;
}
class Program
{
static void Main(string[] args)
{
var wr = AllocateSomeStuff();
Console.WriteLine("Before GC MyClass is in gen " + GC.GetGeneration(wr));
GC.Collect(1);
Console.WriteLine("GC done");
GC.WaitForPendingFinalizers();
Console.WriteLine("Finalizers done");
Console.WriteLine("After GC MyClass is in gen " + GC.GetGeneration(wr));
}
private static WeakReference<MyClass> AllocateSomeStuff()
{
var array = new TheEntry[11000];
for (int i = 0; i < array.Length; ++i)
{
array[i].TheClass = new MyClass { Value = i };
}
return new WeakReference<MyClass>(array[0].TheClass);
}
}
When I run this with an array of 11,000 elements on a 64-bit system (so it's over the 85KB threshold for LOH) the finalizers for MyClass do not run. Output:
Before GC MyClass is in gen 0
GC done
Finalizers done
After GC MyClass is in gen 1
With 10,000 elements they run. I suppose I expected the runtime to GC MyClass as it's unreachable, even though it's still referenced by another unreachable object. Is this how it's supposed to work?