4

I have written a simple .net forms application to test some behaviour of .NET about how it handles memory together with the garbage collector to do the cleaning.

The forms application GUI looks like this:

Application GUI

And the code behind like this:

public partial class Form1 : Form
{
    private readonly IList<byte[]> _btyList = new List<byte[]>();

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        int i = 0;

        while (i < 3)
        {
            byte[] buffer = File.ReadAllBytes(@"C:\PFiles\20131018_nl_metro holland.pdf");
            _btyList.Add(buffer);
            i++;
        }
    }

    private void button2_Click(object sender, EventArgs e)
    {
        int i = 0;

        while (i < _btyList.Count)
        {
            _btyList[i] = null;
            i++;
        }
    }

    private void button3_Click(object sender, EventArgs e)
    {
        GC.Collect();
    }
}

When I add a couple of byte arrays to the private list of byte arrays it (of course) has effect on the memory usage of the application:

Memory usage after adding byte arrays

Now when I press the Clear memory button the memory usage will stay the same. I can wait for hours, but it doesn't change. If I press the Garbage collect button (after Clear memory) it will free the memory immediately:

Memory usage after garbage collect

The question is: Why does the garbage collector not work in this case?

varocarbas
  • 12,354
  • 4
  • 26
  • 37
Kees
  • 1,408
  • 1
  • 15
  • 27
  • 1
    It isn't running because it doesn't need to. If memory is not low, there is no need to collect. – Mitch Wheat Oct 19 '13 at 09:15
  • It needs to run, because 360MB of memory is in use for nothing. – Kees Oct 19 '13 at 09:16
  • 5
    "It needs to run, " - says who? If you have 4GB of memory. 360MB is probably below the collection threshold. Just because you assume it works a particular way does not make it true. You shouldn't, in general, ever worry or think about when the GC runs, unless you are writing critical code. – Mitch Wheat Oct 19 '13 at 09:18
  • What will be the threshold value? Will it work when I put 2GB of data in memory? – Kees Oct 19 '13 at 09:21
  • That's for the GC to decide.... – Mitch Wheat Oct 19 '13 at 09:21
  • 2
    Don't bother, the Garbage collector was already tested by experts. – H H Oct 19 '13 at 09:27
  • 1
    @HenkHolterman: Sure, but if you use it you will have to know how it exactly works... – Kees Oct 19 '13 at 09:32
  • 1
    Very few people know _exactly_ how it works. Most important thing to know: never call `GC.Collect()`. – H H Oct 19 '13 at 09:34
  • And if you want to become some of those people who exactly know you will have to start somewhere to see in practice how it works and eventually ask questions and learn. – Kees Oct 19 '13 at 09:36
  • 1
    No, you start by googling `fReachable` and read everything you find. – H H Oct 19 '13 at 09:38
  • Thanks for the fReachable suggestion. Very interesting! – Kees Oct 19 '13 at 12:01

4 Answers4

4

The garbage collector isn't running because it doesn't need to. If memory is not low, there is no need to collect.

If you have 4GB of memory, 360MB is probably below the collection threshold.

You shouldn't, in general, ever worry or think about when the GC runs, unless you are writing time or memory critical code.

Ref.: Fundamentals of Garbage Collection

Understanding Garbage Collection in .NET

Garbage collection occurs when one of the following conditions is true:

  • The system has low physical memory.

  • The memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs.

  • The GC.Collect method is called. In almost all cases, you do not have to call this method, because the garbage collector runs continuously. This method is primarily used for unique situations and testing.

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
3

Garbage collection is relatively slow and the CLR won't do it every time memory becomes free, only when it needs to. So "freeing" your array doesn't trigger it.

Your experiment has been a complete success: you have learned something about how garbage collection works.

David Arno
  • 42,717
  • 16
  • 86
  • 131
  • Now that was the exact idea about it. – Kees Oct 19 '13 at 09:23
  • If I add 1GB to memory and clear the memory and repeat this action a couple of times it will throw an OutOfMemory exception. How to explain that? The garbage collector still doesn't work? – Kees Oct 19 '13 at 09:27
  • 1
    @KeesdeWit - read up about fragmentation and the Large Object Heap. – H H Oct 19 '13 at 09:29
  • that's a different problem... see Large Object Heap – Mitch Wheat Oct 19 '13 at 09:29
  • 1
    @MitchWheat, that's the problem with using a subjective term like "slow". It's all relative. However having the GC run every time a null was assigned would have a detrimental effect in many situations. Or am I misunderstanding its potential impact? – David Arno Oct 19 '13 at 09:50
  • @David Arno: you state: "Garbage collection is slow " - that's not entirely accurate. Gen 2's are slow. Also assigning to null does nothing useful. A mark and sweep looks for objects that are not referred to by others. – Mitch Wheat Oct 19 '13 at 10:05
  • @MitchWheat, it is entirely accurate. I tried to accommodate your petty pedantry, but I see it was a waste of my time to do so. – David Arno Oct 19 '13 at 10:51
  • It is not accurate. A full garbage collection is relatively expensive. Perhaps add the word full? Gen 0 collections happen frequently, and you don't see everything grinding to a halt... – Mitch Wheat Oct 19 '13 at 10:51
  • 1
    I have "clarified" my answer by changing slow to "relatively slow". To my mind, this is a silly change as slow is an inherently relative term and so I've created a tautology. It might make those who insist my answer is somehow wrong without it happier though. – David Arno Oct 19 '13 at 11:13
2

Its behaving correctly. Garbage collector run is indeterministic, you can't be sure at what time it will run.

GC will collect memory once it feels need that new memory allocation requires some previous allocated memory to be freed.

From MSDN -

Each time you create a new object, the common language runtime allocates memory for the object from the managed heap. As long as address space is available in the managed heap, the runtime continues to allocate space for new objects. However, memory is not infinite. Eventually the garbage collector must perform a collection in order to free some memory. The garbage collector's optimizing engine determines the best time to perform a collection, based upon the allocations being made. When the garbage collector performs a collection, it checks for objects in the managed heap that are no longer being used by the application and performs the necessary operations to reclaim their memory.

Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
2

The GC runs when a trigger happens. A trigger might be a failing allocation or a low-memory event generated by Windows.

Setting a variable to null is not tracked in any way and is not a trigger.

Your case is a valid case for using GC.Collect because you have knowledge about your deallocation patterns that the GC does not have.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Even then, this should only be done if problems are encountered. Running GC.Collect also erases the GC statistics and might result in a performance hit later on. – Eli Algranti Oct 19 '13 at 09:34
  • @EliAlgranti valid point. On the other hand the memory will never be returned by the CLR on its own (except if the OS fires the low-memory event, which is far too late - now the file cache has already been wiped out for example). I'd hate a desktop application to do that to my system. – usr Oct 19 '13 at 09:49