MANAGED DEBUGGING with WINDBG. Managed Heap. Part 1

Hi all,

This post is a continuation of MANAGED DEBUGGING with WINDBG. Thread Stacks. Part 2.

MANAGED HEAP. Part 1

.NET won’t use the NT Heap. It saves some virtual memory for its own managed heap instead.

Objects in the managed heap are part of a Garbage Collector (GC) generation. We have Gen 0, 1 & 2.

All objects start in Gen 0 unless they are over 85,000 bytes in which case they end up in the Large Object Heap (LOH, also known as Gen 3). 85,000 bytes refers to the actual size of the object, not including child objects (member variables). If a member of an object is a reference to another object, the size of that reference is only 4 bytes (in 32 bit machines). Most commonly the items that we will see in the Large Object Heap are strings and large arrays.

When an object is allocated, the GC checks if we have reached one of the generational limits. If so, a GC is triggered. The generational limits are dynamically changed throughout the life of the process in order to better suit the allocation pattern of the application.

If an object is still referenced (“rooted”) during a garbage collection, it is promoted/moved into the next higher generation. The exception is objects in Gen 2 and LOH. Garbage collections on Gen 2 are less frequent than on Gen 1, and on Gen 1 than on Gen 0.

We use LOH for large objects for two main reasons: 1) it is assumed that if you allocate a large object, you are likely to keep it around for a while, 2) large objects are expensive to move. For these reasons LOH is collected less frequently, together with Gen2. Additionally, LOH is not compacted. Use LOH wisely!

Note that if an object is not referenced but has a Finalize (destructor) method, it will automatically get promoted to the next gen and clean up in that gen, unless we call its Dispose method. Dispose will call Finalize and instruct the GC to forget about it (by calling GC.SuppressFinalizer) and clean up the object in its current gen. Calling Dispose guarantees we clean up an object ASAP.

· We can take a look to the .NET heap:

We can see how much memory is taken up by the managed heap and its parts: generations, large object heap, etc.:

0:004> !EEVersion

2.0.50727.1433 retail

Workstation mode

SOS Version: 2.0.50727.42 retail build

0:004> !EEHeap -gc

Number of GC Heaps: 1

generation 0 starts at 0x01901018

generation 1 starts at 0x0190100c

generation 2 starts at 0x01901000

ephemeral segment allocation context: none

segment begin allocated size

00423878 7b463c40 7b47a744 0x00016b04(92932)

0041d178 7a733370 7a754b98 0x00021828(137256)

003d6600 790d8620 790f7d8c 0x0001f76c(128876)

01900000 01901000 01949ff4 0x00048ff4(298996)

Large object heap starts at 0x02901000

segment begin allocated size

02900000 02901000 02907df0 0x00006df0(28144)

Total Size 0xa787c(686204)

------------------------------

GC Heap Size 0xa787c(686204)

Note that workstation applications will only have 1 heap (see above), but server applications (ASP.NET) will have 1 heap per processor (HyperThreading aware).

0:028> !EEVersion

2.0.50727.1433 retail

Server mode with 2 gc heaps

SOS Version: 2.0.50727.42 retail build

0:028> !EEHeap -gc

Number of GC Heaps: 2

------------------------------

Heap 0 (02ec2c48)

generation 0 starts at 0x04722600

...

Heap Size 0xc8048(819272)

------------------------------

Heap 1 (02ec38e0)

generation 0 starts at 0x086f4264

...

Heap Size 0x4a248(303688)

------------------------------

GC Heap Size 0x112290(1122960)

In workstation apps, GC will take place in the thread that tries to allocate memory and can’t. In server apps there will be one dedicated GC thread per heap/processor.

· We can check if Preemptive GC mode is enabled on a thread:

0:004> !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 1d14 005a6e80 6020 Enabled 0299c3e8:0299dfe8 00571520 0 STA

2 2 1b34 005a8e00 b220 Enabled 00000000:00000000 00571520 0 MTA (Finalizer)

Preemptive GC indicates what GC mode the thread is in: "enabled" in the table means the thread is in preemptive mode where GC could preempt this thread at any time; "disabled" means the thread is in cooperative mode where GC has to wait the thread to give up its current work (the work is related to GC objects so it can't allow GC to move the objects around). When the thread is executing managed code (the current IP is in managed code), it is always in cooperative mode; when the thread is in Execution Engine (unmanaged code), EE code could choose to stay in either mode and could switch mode at any time; when a thread is outside of CLR (i.e. calling into native code using interop), it is always in preemptive mode.

Summing up, if a thread has PreEmptive GC disabled is because it does not want to be interrupted by the GC.

Next post: MANAGED DEBUGGING with WINDBG. Managed Heap. Part 2.

Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.

Regards,

Alex (Alejandro Campos Magencio)