11

I was profiling the memory usage of a Windows Forms application in dotmemory and I noticed that for my application there were 0-4 heaps all of varying sizes as well as the large object heap.

I was just wondering if anyone had a good explanation of what each heap is for and what is typically stored in each heap?

CharithJ
  • 46,289
  • 20
  • 116
  • 131
Crunchy234
  • 1,887
  • 2
  • 16
  • 21
  • This might be related to how the C# garbage collector works. I think it's a generational GC, which separates data based on how recently it was used. – ryanyuyu Jul 23 '15 at 21:27
  • 2
    There are 3 heaps + the large object heap. All objects are allocated in heap 0. If they survive one garbage collection they're promoted to heap 1, then to heap 2, and then stays there until collected. The large object heap (LOH) is for objects which are 85000 bytes or more in size (not in total, continous, like arrays). Where you got the fifth heap from I don't know. – Lasse V. Karlsen Jul 23 '15 at 21:32
  • Might be of interest: http://stackoverflow.com/questions/11189932/stack-and-heap-allocation – Davin Tryon Jul 23 '15 at 21:32
  • Eric Lippert wrote a great blog article (in 2 parts) about both the stack and the heap and how it works. Look here: http://blogs.msdn.com/b/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx – Icemanind Jul 23 '15 at 21:33
  • 3
    Not sure what the down/close votes on this question are for - seems clear enough, is objectively answerable and would be a very useful question to have an answer for on SO. It could do with a bit of retargeting though. – Ant P Jul 23 '15 at 21:35
  • The 5th heap might be an unmanaged heap, for interop and possibly internal use by the CLR. – Dan Bryant Jul 23 '15 at 21:47
  • @DanBryant There was a specified "Unmanaged memory" section which I could see changing as I called my C++ DLL I wrote which is completely unmanaged code. It also present before I started using the DLL so maybe it is used by the CLR or some native code that win forms was calling. – Crunchy234 Jul 25 '15 at 03:12
  • For anyone looking for the Eric Lippert article referred to earlier in the comments, it's currently at [The Stack Is An Implementation Detail](https://learn.microsoft.com/en-us/archive/blogs/ericlippert/the-stack-is-an-implementation-detail-part-one). – Andrew Morton Oct 29 '20 at 12:41

3 Answers3

20

The other answers seem to be missing the fact that there is a difference between heaps and generations. I don't see why a commercial profiler would confuse the two concepts, so I strongly suspect it's heaps and not generations after all.

When the CLR GC is using the server flavor, it creates a separate heap for each logical processor in the process' affinity mask. The reason for this breakdown is mostly to improve scalability of allocations, and to perform in GC in parallel. These are separate memory regions, but you can of course have object references between the heaps and can consider them a single logical heap.

So, assuming that you have four logical processors (e.g. an i5 CPU with HyperThreading enabled), you'll have four heaps under server GC.

The Large Object Heap has an unfortunate, confusing name. It's not a heap in the same sense as the per-processor heaps. It's a logical abstraction on top of multiple memory regions that contain large objects.

Sasha Goldshtein
  • 3,499
  • 22
  • 35
  • "These are separate memory regions, but you can of course have object references between the heaps and can consider them a single logical heap." - I was searching for how the access between thoses heaps work. Do you have more information about it to share? – Daniel Genezini May 07 '22 at 21:43
8

You have different heaps because of how the C# garbage collector works. It uses a generational GC, which separates data based on how recently it was used. The use of different heaps allows the garbage collector to clean up memory more efficiently.

According to MSDN:

The heap is organized into generations so it can handle long-lived and short-lived objects. Garbage collection primarily occurs with the reclamation of short-lived objects that typically occupy only a small part of the heap.

  • Generation 0. This is the youngest generation and contains short-lived objects. An example of a short-lived object is a temporary variable. Garbage collection occurs most frequently in this generation. Newly allocated objects form a new generation of objects and are implicitly generation 0 collections, unless they are large objects, in which case they go on the large object heap in a generation 2 collection. Most objects are reclaimed for garbage collection in generation 0 and do not survive to the next generation.
  • Generation 1. This generation contains short-lived objects and serves as a buffer between short-lived objects and long-lived objects.
  • Generation 2. This generation contains long-lived objects. An example of a long-lived object is an object in a server application that contains static data that is live for the duration of the process.

Objects that are not reclaimed in a garbage collection are known as survivors, and are promoted to the next generation.

Important data quickly gets put on the garbage collector's back burner (higher generations) and is checked for deletion less often. This lowers the amount of time wasted checking memory that truly needs to persist, which lets you see performance gains from an efficient garbage collector.

ryanyuyu
  • 6,366
  • 10
  • 48
  • 53
  • 4
    This does not explain why there can be different heaps, where each of the heaps has generations. – Thomas Weller Aug 22 '17 at 11:27
  • in .NET 5 was added POH (Pinned object heap), and there's a great article to read about it's internals (and about SOH and LOH aswell) - https://devblogs.microsoft.com/dotnet/internals-of-the-poh/ – Eugene Zakharov Jul 18 '22 at 12:01
1

When it comes to managed objects, there are three Small Object Heaps(SOH) and one Large Object Heap(LOH).

Large Object Heap (LOH)

Objects that are larger than 85KB are going to LOH straight away. There are some risks if you have too many large objects. That's a different discussion, for more details have a look at The Dangers of the Large Object Heap

Small Object Heap (SOH) : Gen0, Gen1, Gen2

Garbage collector uses a clever algorithm to execute the garbage collecton only when it is required. Full garbage collection process is an expensive operation which shouldn't happen too often. So, it has broken its SOH into three parts and as you have noticed each Gen has a specified amount of memory.

Every small object (<85KB) initially going to Gen0. When Gen0 is full, garbage collection executes only for Gen0. It checks all instances that are in Gen0 and clears/releases memory that is used by any unnecessary objects(non-referenced, out of scoped or disposed objects). And then it copies all the required (in used) instances to Gen1.

Above process is actually occurs even when you execute below: (not required to call manually)

// Perform a collection of generation 0 only.
GC.Collect(0);

In this way, Garbage collector clears the memory that are allocated for short lived instances first (strings which is immutable, variables in methods or smaller scopes).

When GC is keep doing this operation at one stage, Gen1 overflows. Then it does the same operation to Gen1. It clears all the unnecessary memory in Gen1 and copies all required ones to Gen2.

Above process is occurs when you execute below manually (not required to call manually)

// Perform a collection of all generations up to and including 1.
GC.Collect(1);

When GC is keep doing this operation at one stage if Gen2 overflows it tries to clean Gen2.

Above process is occurs even when you execute below manually (not required to do manually)

// Perform a collection of all generations up to and including 2.
GC.Collect(2);

If the amount of memory needs to be copy from Gen1 to Gen2 is greater than the amount of memory available in Gen2, GC throws out of memory exception.

CharithJ
  • 46,289
  • 20
  • 116
  • 131