Eugeniy  Maksimov
Eugeniy Maksimov

Reputation: 1694

.Net Why GC does not remove objects from heap?

I've written simple test program:

namespace GCTest {
class Program {
    static void Main(string[] args) {
        var a1 = new A();
        a1.AProperty = new A();
        a1.AProperty.AProperty = new A();
        a1.AProperty.AProperty.AProperty = new A();
        a1.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a1 created");
        Console.ReadKey();

        var a2 = new A();
        a2.AProperty = new A();
        a2.AProperty.AProperty = new A();
        a2.AProperty.AProperty.AProperty = new A();
        a2.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a2 created");
        Console.ReadKey();

        var a3 = new A();
        a3.AProperty = new A();
        a3.AProperty.AProperty = new A();
        a3.AProperty.AProperty.AProperty = new A();
        a3.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a3 created");
        Console.ReadKey();

        var a4 = new A();
        a4.AProperty = new A();
        a4.AProperty.AProperty = new A();
        a4.AProperty.AProperty.AProperty = new A();
        a4.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a4 created");
        Console.ReadKey();

        var a5 = new A();
        a5.AProperty = new A();
        a5.AProperty.AProperty = new A();
        a5.AProperty.AProperty.AProperty = new A();
        a5.AProperty.AProperty.AProperty.AProperty = new A();

        Console.WriteLine("a5 created");
        int a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        Console.ReadKey();

        a1 = a2 = a3 = a4 = a5 = null;
        Console.WriteLine("a1-a5 are null");
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();
    }
}

class A {
    int[] arr = new int[1000];
    public A() {
        arr[0] = 1;
    }

    public A AProperty { get; set; }
}

}

I make snapshots using build-in Visual Studio 2017 diagnostic tools. When a1 - a5 variables are initialized, there are 25 A instances in the heap, as expected:

enter image description here

But after variables a1-a5 become equal to null, even after additional calls of GC.Collect method, the heap still contains A instances. And more calls of GC.Collect doesn't reduce A objects in the heap. Why? Is there way to force collect ALL unused objects?

enter image description here

EDIT: I've switched to the Release configuration and I see that in the heap there are 15 objects after initializing a1-a5 variables:

enter image description here

And after GC.Collect calls still remains 14 objects.

Upvotes: 2

Views: 289

Answers (1)

Brian Rasmussen
Brian Rasmussen

Reputation: 116401

I don't know how VS handles this, but if I run your code using a release build I don't see any instances of A on the heap when inspecting it using SOS.

To see for yourself, download WinDbg. Then attach to your process and load SOS.

.loadby sos clr

You can dump the entire heap using !dumpheap -stat or just the type using !dumpheap -type GCTest.A.

You should see instances going up as they are created. Once GC has reclaimed the instances you shouldn't see any instances of A on the heap.

Here's the output following the first GC after all references are nulled.

0:001> !dumpheap -type GCTest.A
 Address       MT     Size

Statistics:
      MT    Count    TotalSize Class Name
Total 0 objects

Upvotes: 4

Related Questions