tags:

views:

104

answers:

2

I wrote this little test program:

using System;

namespace GCMemTest
{
    class Program
    {
        static void Main(string[] args)
        {
            System.GC.Collect();

            System.Diagnostics.Process pmCurrentProcess = System.Diagnostics.Process.GetCurrentProcess();

            long startBytes = pmCurrentProcess.PrivateMemorySize64;

            double kbStart = (double)(startBytes) / 1024.0;
            System.Console.WriteLine("Currently using " + kbStart + "KB.");

            {
                int size = 2000000;
                string[] strings = new string[size];
                for(int i = 0; i < size; i++)
                {
                    strings[i] = "blabla" + i;
                }
            }

            System.GC.Collect();

            pmCurrentProcess = System.Diagnostics.Process.GetCurrentProcess();
            long endBytes = pmCurrentProcess.PrivateMemorySize64;

            double kbEnd = (double)(endBytes) / 1024.0;
            System.Console.WriteLine("Currently using " + kbEnd + "KB.");

            System.Console.WriteLine("Leaked " + (kbEnd - kbStart) + "KB.");
            System.Console.ReadKey();
        }
    }
}

The output in Release build is:

Currently using 18800KB.
Currently using 118664KB.
Leaked 99864KB.

I assume that the GC.collect call will remove the allocated strings since they go out of scope, but it appears it does not. I do not understand nor can I find an explanation for it. Maybe anyone here?

Thanks, Alex

+6  A: 

You're looking at the private memory size - the managed heap will have expanded to accommodate the strings, but it won't release the memory back to the operating system when the strings are garbage collected. Instead, the managed heap will be bigger, but have lots of free space - so if you create more objects, it won't require the heap to expand.

If you want to look at the memory used within the managed heap, look at GC.GetTotalMemory. Note that due to the complexities of garbage collection, there's a certain amount of woolliness within all of this.

Jon Skeet
Is it true that calling `GC.Collect` may or may not do a garbage collection depending on some hidden variable?
Phil
@Phil: I wouldn't say that it's based on a "hidden variable" but it's not guaranteed to actually do the collection now. It's a request rather than an order, if you will.
Jon Skeet
A: 

Indeed I used the private mem size because that's the one that's closest to the one in Process Explorer

if I rewrite the program with the GC.GetTotalMemory like this:

using System;

namespace GCMemTest
{
    class Program
    {
        static void Main(string[] args)
        {
            System.GC.Collect();

            long startBytes = System.GC.GetTotalMemory(true);

            {
                string[] strings = new string[2000000];
                for (int i = 0; i < 2000000; i++)
                {
                    strings[i] = "blabla" + i;
                }
                strings = null;
            }

            System.GC.Collect();

            long endBytes = System.GC.GetTotalMemory(true);

            double kbStart = (double)(startBytes) / 1024.0;
            double kbEnd = (double)(endBytes) / 1024.0;

            System.Console.WriteLine("Leaked " + (kbEnd - kbStart) + "KB.");
            System.Console.ReadKey();
        }
    }
}

Then the output is:

Leaked 0KB.

Only when I have 'strings = null;' this is the case, remove it and I leak 100MB. This means that the local scope in the main routine does not cause the array to be freed. If I move that part into a static method Test, and call that one instead, I leak a few bytes. I guess what I should learn from this is that local scopes are ignored by the GC.

ava
Were you running under the debugger by any chance? (Even in a release build?) That affects GC.
Jon Skeet
I was, but without it has the same result.
ava