I have tried a simple experiment to verify the functionality of the garbage collector. Referencing 3.9 Automatic memory management (MSDN) about automatic memory managem
Some of the information is already included in the article you link to. There are several indications that the behavior you observe is correct:
... the garbage collector may (but is not required to) treat the object as no longer in use.
... at some unspecified later time ...
One important thing, at least for the old (non-concurrent) version of the Garbage collector is, that the garbage collector runs on a different thread. You can verify that in the debugger:
0:003> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
PreEmptive GC Alloc Lock
ID OSID ThreadOBJ State GC Context Domain Count APT Exception
0 1 1b08 0058f218 a020 Enabled 025553ac:02555fe8 0058b868 1 MTA
2 2 1e9c 005a78c8 b220 Enabled 00000000:00000000 0058b868 0 MTA (Finalizer)
The Finalizer thread performs the garbage collection. All other threads are suspended during the operation, so that no thread can modify objects during the time of reorganization.
But why is that important?
It explains why the garbage collection does not apply immediately, neither in your scenario nor if you call GC.Collect()
to do the garbage collection. For the garbage collector to run, you also need a thread switch. So, the minimum code needed for a non-concurrent garbage collection is
GC.Collect();
Thread.Sleep(0);
If you're concerned about memory management, be sure to also check out the awesome answer about IDisposable.
Also, nobody has explained yet, that looking at the memory consumption with Task Manager is not reliable.
.NET acts directly on virtual memory, i.e. uses the virtual memory manager. It does not use the heap, i.e. the heap manager. Instead it uses it's own memory management, called managed heap.
.NET gets the memory from Windows (the kernel). Assume it gets a fresh piece of memory from Windows, which has no .NET objects inside. From Windows' point of view, the memory is gone (given to .NET). However, from .NET point of view, it's free and can be used by objects.
Again, you can observe that in the debugger:
0:003> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 60 71cb9000 ( 1.778 Gb) 88.91%
84 986f000 ( 152.434 Mb) 67.09% 7.44%
Image 189 2970000 ( 41.438 Mb) 18.24% 2.02%
...
What is reported as
is virtual memory from Windows point of view. In this case, 150 MB are used.
0:003>!dumpheap -stat
...
00672208 32 8572000 Free
...
So you can see that 8.5 MB are free from .NET point of view, but have not been given back to Windows (yet) and will still be reported as used there.
If you have not modified Task Manager's default column settings, it's even worse, because it will show the Working Set, which is memory in RAM only. However, some of the memory may have been swapped to disk, thus it may not be reported by Task Manager.