The Setup
.NET allocates memory for each generation’s heap (0, 1, 2, LOH) in segments to get a continuous block of memory, on startup, and when it attempts to satisfy an allocation request, after a collection.
This memory allocated for each heap will likely level off as the application “warms up”, except potentially for generation 2, and large object heap. During a garbage collection, each heap (0, 1, 2) is swept and compacted, except for the large object heap (LOH), which is just swept.
I understand the ‘sweep’ part of a collection to mean that the GC identifies which objects are no longer rooted and are available for collection (or finalization) and that ‘compact’ means that the addresses that are still alive in a heap are reorganized so that the available remaining heap has more continuous memory available to it.
As the budget for each segment within the heap is exceeded, .NET will allocate another segment in order to fulfill allocations if it can.
The Question
My question comes down to what happens to that memory in each heap, that is not be used by the application (committed) any longer, but is still reserved by .NET? When is it released back to the OS?.
I believe this to be the scenario where a process might appear to be consuming a lot of memory (virtual size is quite large, but private bytes small), but when inspecting its heaps are mostly free space. As another caveat, the total size of the heaps may also be quite small, and not account for the memory being consumed by the process.
There is no blocked finalizer and all looks healthy for a process - it may have been running for weeks before it triggered a monitor alert (e.g.).
Trying for further clarification of the question, if you read Tess .NET Memory Management - A Restaurant Analogy, if the tables are heap segments, does the restaurant ever lose tables (e.g. free heap segments)?
Edit
- Removed confusing reference to working set and chickens
- Added reference to Tess restaurant analogy
My answer is - it doesn't matter. The OS gives the application (within which the .NET runtime runs) virtual memory. This is not 'real' memory. The OS can put each page of virtual memory wherever it likes - on the processor, in main memory, on the disk. So, it is possible for an app to use more memory than the amount of RAM on the system, and the OS will copy the bits that are needed to/from disk to ensures the app keeps running (modulo certain addressing & technical limitations).
The OS manages the virtual memory of all the processes on the system, and ensures that one program doesn't hog all the RAM on the system to the detriment of other programs. When the .NET runtime asks for memory from the system for use in the heaps, but then doesn't use it, this memory will (if the system is low on free RAM) be moved to disk, because it isn't being accessed.
Clarification via email from Tess: (emphasis mine)
The segment sizes stay the same throughout the course of the application, but there are two things to consider here.
The allocations for a segment is a virtual allocation, meaning that while we reserve virtual memory, we only commit what we actually use, so the private bytes used for a segment is not the same as the segment size, this means that after a GC, your private bytes will go down, while your virtual bytes will stay the same.
When a segment is no longer used, i.e. if you happen to GC everything in a segment so that it no longer contains any .net objects, the virtual alloc is returned back to the OS.
Taking that on faith, then heap segments (restaurant tables) will be returned back to the OS.
I don't know the answer to this, but I suspect that .NET doesn't release its heaps until the process exits (and possibly on unloading an AppDomain, at a guess). This is only based on my watching the perfmon .NET Memory counters. The GC total bytes counters shows, at least in my app, that the Reserved bytes go up and down a little over the life of my app, like about 30MB.
And, if the GC is running in the server mode, perhaps .NET releases memory more often.