Mint blog
cshark_blog_memory-management-in-net-garbage-collector
04/12/2019

Memory Management in .NET - Garbage Collector

We, as .NET developers, do not think about memory management in .NET a lot. We think about Garbage Collector even less. And that’s fantastic. GC has been designed to make it useful without even thinking about allocating and deallocating memory when it is no longer needed. The majority of developers don’t bother with collecting garbage as far as they don’t run into trouble with it.

Before we dive into how garbage collection works in .NET, let’s remember how it looks like in old, but still good, C++. Let’s assume we want to create a variable, which holds the username and displays it on the screen. Our code will look somehow like that:

 #include  #include   void showUsername() {    char *username;    username = (char *) malloc(8);    strcpy(username, "username");    printf("%s", username); }  int main() {   showUsername(); } 

When the variable which holds the username is no longer needed, we need to free the memory taken by it by calling:

    free(username); 

Meanwhile in C# we do:

 var username = "username";    Console.WriteLine(username); 

See the difference? Business logic is missing somewhere between memory management pieces of code. But it's getting more complicated when we have more functions, classes, and variables. How can we know that memory taken by a variable is no longer needed and can be freed? Without proper Garbage Collection, we run into serious issues with code readiness and memory leaks.

Memory leak might occur when we anyhow release memory, which is still used, or we do not release memory, which is not needed anymore (we eventually get into “out of memory” issues quickly). Does it mean that we won't have memory leaks with the Garbage Collector? Of course, we will. We can cause memory leaks in .NET in many different ways, for example, by not unsubscribing to events, or by caching resources infinitely. That's called Managed Memory Leak.

Deep dive into .NET Garbage Collector

In .NET, Garbage Collector is part of Common Language Runtime (there are more GC versions, but we keep our focus CLR here) and its source code can be found on Github (with only two classes, more than 1MB and almost 40000 lines of code, it is not cleanest code if you ask me). To explain the essentials of GC, we need to explain some terms to be on the same page.

When is the Garbage Collector being triggered?

Really common mistake done by beginning .NET developers is the answer to the question “How long will this variable live in the memory?”. Some people think that Garbage Collection will be triggered after leaving some scope, and every variable from that scope will be collected then. The truth is, it won’t. It will be run with memory allocation (or manually, by calling GC.Collect()). That’s because the Garbage Collector is pretty smart, and it won’t collect anything as far as it’s really needed.

Allocator

When you want to assign value or reference to the variable, you need to allocate memory necessary for that assigned value. Basically, it’s about writing it under some memory address. The allocator is the one who is responsible for allocating memory for new objects. If we consider a simple scenario, where we have some memory allocated for objects called O1, O2, ..., On at the end of it, we have Next Object Pointer, which points the memory address where the next object will be allocated. After allocating that memory, the pointer will be moved just right after On + 1 object. Meanwhile, in C++ for example, we need to find free memory with size at least big as a resource, which costs more.

cshark_blog_memory-management-in-net-garbage-collector_allocator

Tracing

Core CLR Garbage Collector uses Tracing algorithm, which contains three different stages:

Marking

Before we can deallocate memory, we need to find out which memory is no longer used. GC is doing that by marking, which is looking for objects no longer referenced in other objects and marking them as ready to deallocate.

cshark_blog_memory-management-in-net-garbage-collector_marking

Compacting

After we find out which objects are no longer needed, we can make more space for a new one. Compacting is removing all these dead objects and reallocating them within a single, continuous area by moving them next to each other.

cshark_blog_memory-management-in-net-garbage-collector_compacting

Sweeping

If we want to free memory to cost less, we can do sweeping, which just deallocate memory marked in the marking stage. But be aware, it makes our memory fragmented (see where the next object pointer is).

cshark_blog_memory-management-in-net-garbage-collector_sweeping

Generations and Object Heaps

Every .NET developer who heard about GC at least once also heard about something called Generations and Heaps.

We have two heaps in the Garbage Collector: Small-Object Heap (which contains generation 0, generation 1 and generation 2) and Large-Object Heap (which contains all objects larger than 85'000 bytes).

cshark_blog_memory-management-in-net-garbage-collector_heaps

Small-Object Heap

Whenever GC is being run in SOH, it marks and compacts or marks and sweeps (whichever suits best at that moment). Every new object is allocated in generation 0 and will be promoted to generation 1 when, after collecting garbage, there is some reference to this object. Of course, the same thing with generations 1 and 2.

Because generations 0 and 1 are in the cache memory, cleaning them is way more efficient than cleaning generation 2 and large-object heap. Microsoft claims it takes no longer than 1 millisecond.

cshark_blog_memory-management-in-net-garbage-collector_small-heaps

Large-Object Heap

Before .NET 4.5, LOH used to use simple allocator, as illustrated above with Mark & Sweep only. Nowadays, it's a bit smarter, and it looks for free memory to avoid fragmentation, similar to what's done in C++. We are talking about objects which are at least 85'000 bytes, so it could be fragmented a lot. Fortunately, after .NET 4.5.1 we can do single Compact on demand, by calling simple two lines of code:

 GCSettings.LargeObjectHeapCompactionMode =  GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); 

But because we are still talking about Large-Object Heap, compacting might take a while, so be careful as it might freeze your application for a moment.

Summary

Hopefully, we don't need to think about all of these on a daily basis. But it's still good to know how memory management works in .NET. Thanks to that, we can focus on delivering business value without remembering when and how we should allocate or deallocate our memory. Knowledge about Garbage Collector is often really useful during interviews, and now you know how it works.

cshark-people-konrad-zajda
Konrad Zajda
A Fullstack Developer with .NET Core at CSHARK. Delivering user-focused solutions with the newest technologies. Passionate about software quality, low latency systems, communication and leadership.
07/07/2020
CSHARK Rated by Clutch as one of Poland’s Top Software Developers for 2020
read more
29/06/2020
9 Common Project Management Challenges in Software Development
read more