C# .NET — Garbage Collector
Recycle Trash by Roundicons.com from NounProject.com

Garbage Collector

Karol Rossa
6 min readDec 9, 2021

--

The garbage collection process is more complex than you may think. The simplest explanation is that GC manages your application’s allocation and memory release for managed code. Many developers believe the collection process is beyond their control which is not valid.

Let me show you how GC is working. It will allow you to write your code to achieve the best performance. Also, questions about GC are popular during interviews :)

The following articles will guide implementing the IDispose interface and Finalize method.

Automatic Process

To understand what we can do, let’s first see what is happening automatically. Each of the following conditions can trigger GC:

  • The system has low physical memory. OS or host send low memory notification.
  • The memory used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs.
  • When you call the GC.collect method, in most cases, you don’t have to call it because the garbage collector runs continuously. This method is primarily used for unique situations and testing.

Runtime reserves a contiguous region of address space for the new process. In the .NET world, this is known as Managed Heap. Initially, the pointer is set to the base address. Now our application is ready for allocation. After creating a reference type, GC allocates memory at the base address. The garbage collector allocates memory immediately after the previous object for the next one. The garbage collector releases the memory for references no longer used in the application during collection. GC uses a memory-copying function to compact reachable objects as the last step. (Fig. 1)

Collection process

Generations

Garbage collector divides objects into generations. We can further split into groups. (Fig. 2)

  • short-lived objects
  • long-lived objects
  • large objects

Generations 0 and 1, also known as ephemeral, contain short-lived objects. If they live long enough, they will eventually get promoted to generation 2. The first three generations are located on a Small Object Heap (SOH). Large objects are treated differently than the rest. They are placed on a Large Object Heap (LOH), sometimes called generation 3. Generations provide a logical view of the GC heap. The algorithm uses this logic to be more efficient.

  • It’s faster to compact the memory for a portion of the managed heap than for the entire managed heap.
  • Newer objects have shorter lifetimes, and older ones have longer lifetimes.
  • Newer objects tend to be related to each other and accessed by the application simultaneously.

Small object heap

Garbage collection primarily occurs with the reclamation of short-lived objects. All new objects get allocated in generation 0, and in a well-tuned application, most of them will die there. If an application attempts to create a new object when gen0 is full, the garbage collector performs a collection. At first, only gen0 is examined, and in most cases, this is enough. Objects that survive are promoted to gen1 and ultimately to gen2. Gen1 serves as a buffer between short-lived and long-lived objects.

Objects that survive the first round of collection tend to have longer lifetimes. The collection process of gen0 does not examine promoted objects because it occurs on the triggered generation and all below.

Generation 2 contains long-lived objects. A good example is an object with static data in a server application. Objects that survive collection in gen2 are no longer promoted. They only get deallocated once they are no longer reachable by an application.

Figure 3 illustrates the process of collecting objects in SOH. Survivors Obj 1 and Obj 3 from gen0 form gen1. Then new objects are allocated to gen0. After the following collection, survivors Obj 5 and Obj 7 are moved to gen1, and Obj 1 is moved to long-lived gen2.

Large object heap

If a new object is larger than 85,000 bytes, it will be allocated on LOH instead of generation 0. We can call a LOH generation 3. It is a separate physical generation that’s logically collected only as part of gen2. Survivors from gen2 and gen3 remain in their respective generations. Objects from LOH by default are not compacted after collection because it would be too expensive. This means that if an object allocated between two others gets collected. It leaves remaining free space. GC can only use this space to allocate smaller objects than the remaining free space.

Following conditions will trigger LOH collection:

  • Allocation exceeds the generation 2 or large object threshold.
  • You call the GC.Collect method.
  • The system is in a low memory situation.

LOH performance

Allocating objects on LOH can have an impact on performance. CLR must clear memory for every new object. Collection can be triggered by either gen 2 or LOH hitting the threshold. You have to be cautious because large object allocation can trigger GC and if your gen 2 heap is large, then checking it can affect garbage collection time. Also, if you have many objects in SOH, it can trigger the collection of gen 2 and, by implication, LOH. If your application takes too much time for GC, this is worth checking out.

LOH, in most cases, contains arrays. If an array contains reference-rich objects, it incurs a cost that is not present if elements are not reference-rich. During garbage collection, each reference needs to be checked. GC does not need to go through the elements in a reference-free array at all. If you need to work with many large arrays, it is best to utilize an array pool.

Workstation or Server Garbage Collection

The garbage collector is self-tuning and works well in many scenarios. Now we will discuss the significant settings available to you. You can choose between Workstation and Server garbage collection. Both can be concurrent or background, which is the default option. The workstation was designed for client applications and is the default option for standalone apps. The server garbage collector is intended for server applications that need high throughput and scalability. A host determines the default GC flavor for hosted apps (ex. ASP.NET).

Here are some key differences between both flavors:

Workstation GC

  • The collection occurs on the user thread that triggered it and remains the same priority. They typically run at normal priority, so GC must compete with other threads for CPU time.
  • Workstation is always used on computers with only one processor, regardless of configuration.

Serve GC

  • The collection occurs on multiple threads running at the highest priority level.
  • A heap and dedicated thread are provided for each CPU, and they are collected simultaneously. All heaps can be accessed by user code. Objects on different heaps can refer to each other.
  • Because of parallelization Server GC, is faster than Workstation GC on the same heap size.
  • Server garbage collection tends to have more significant size segments. Nevertheless, size is implementation-specific and is subject to change. You should not make assumptions about the segment size.
  • If you are running hundreds of instances of an application, consider using workstation garbage collection with a background setting.

Background GC

Background collection applies only to generation 2 and is enabled by default. It is performed on one or more dedicated threads. Ephemeral generations (0 and 1) collection occurs as needed alongside. Foreground collection (ephemeral) suspends all managed threads.

There are two significant differences between server and workstation background garbage collection. The workstation uses only one dedicated thread, whereas the server can use multiple threads. This number usually matches the number of physical processors. Also, server GC threads do not time out.

You can also enable concurrent GC, allowing threads to run concurrently with a dedicated garbage collection thread. This does not affect generations 0 and 1 as they are always non-concurrent. Concurrent garbage collection enables interactive applications to be more responsive by minimizing pauses for collection.

Configuration

Large object heap can be compacted automatically when the hard memory limit is set.

Additionally, you can compact LOH on-demand using property GCSettings.LargeObjectHeapCompactionMode. Here you can find information on setting the type of GC (workstation/server) GC Flavors. You can always disable background GC if this is desirable in your case. Last but not least, you can enable concurrent GC. There are additional settings like heaps count and size that you can tune for best performance.

--

--