Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
vermeir_nico_introducing_net_6_getting_started_with_blazor_m.pdf
Скачиваний:
19
Добавлен:
26.06.2023
Размер:
11.64 Mб
Скачать

CHAPTER 11

Advanced .NET 6

A lot of the things in .NET that we use on a daily bases are often taken for granted. We reserve memory to store data, and we just assume that that memory gets released at some point. We ask the framework for a thread and we get one, but where does that thread come from? And how does async work again? Let’s go into some more detail and explore how these concepts actually work.

Garbage Collector

One of the greater advantages of writing managed code is the access to a garbage collector, or GC. A garbage collector manages memory usage for you; it allocates memory when requested and releases memory automatically when no longer in use. This helps greatly in preventing out of memory issues; it does not eliminate the risk completely; we as developers need to be smart about memory allocation as well, but it is a great help.

Before we dive into the garbage collector, let’s refresh our memory about memory. Memory consists of two pieces, a stack and a heap. A misconception that has been going around is that one is for value types and the other is for reference types; that is not entirely correct. Reference types do always go on the heap, but value types go where they are declared. Let me clarify.

Every time we call a method, a frame is created; that frame is placed on the stack.

A stack is a tower of frames and we only have access to the top most one; once that frame is finished, it gets removed from the stack and we can continue with the next one. When an error occurs in one of the methods, we often get a StackTrace in Visual Studio; this is an overview of what was on the stack the moment the error occurred. Variables declared in a method usually go on the stack. Let’s use Listing 11-1 as an example to illustrate what happens.

297

© Nico Vermeir 2022

N. Vermeir, Introducing .NET 6, https://doi.org/10.1007/978-1-4842-7319-7_11

Chapter 11 Advanced .NET 6

Listing 11-1.  A simple method with local variables

public int Sum(int number1, int number2)

{

int result = number1 + number2; return result;

}

When calling the method, the Stack will look like Figure 11-1.

Figure 11-1.  Stack when calling Sum method

Let’s expand our example.

Listing 11-2.  Calling a method on a class instance

Math math = new Math();

int result = math.Sum(5, 6);

public class Math

{

public int Sum(int number1, int number2)

{

298

Chapter 11 Advanced .NET 6

int result = number1 + number2; return result;

}

}

This time we are instantiating a class and calling a method on that class. That results in the memory in Figure 11-2.

Figure 11-2.  Stack and heap

The class instance lives on the heap with the stack containing a pointer to the instance. The call to the class member Sum() results in a second frame on the stack where the variables live as well.

The Heap

There are two object heaps in .NET. The large object heap and the small object heap. The small object heap contains objects that are smaller than 85K in size; all the others go on the large object heap. The reason for this split is performance. Smaller objects are faster to inspect so the garbage collector works faster on the small object heap. Objects on the heap contain an address that can be used to point to this object from the stack, hence the name Pointers. What determines the size of an object is beyond the scope of this book, so we won’t go into detail here.

299

Chapter 11 Advanced .NET 6

The Stack

The stack is used to track data from every method call. For every method a frame is created and placed on top of the stack. A frame can be visualized as a box or container, containing all objects, or pointers to those objects, the method creates or encapsulates. After a method returns, its frame is removed from the stack.

Garbage Collection

Back to the garbage collector. The garbage collector is a piece of software included in the .NET runtime that will inspect the heap for allocated objects that are no longer referenced by anyone. If it finds any, it will remove them from the heap to free up memory. This is just one place where the garbage collector works; other places are, for example, global references or CPU registers. These are called GC Roots.

The garbage collection consists of several passes. First the GC will list all GC Roots. It will then traverse all reference trees of the roots, marking the objects that still have references. A second pass will update the references to objects that will be compacted. The third pass reclaims memory from dead objects. During this phase, live objects are moved closer together to minimize fragmentation of the heap. This compacting usually only happens on the small object heap because the large object on the large object heap takes too much time to move. However, compacting can be triggered on the large object heap manually when needed.

The garbage collector runs automatically in a .NET application. There are three possible scenarios in which garbage collection is triggered:

•\

Low memory. Whenever the physical memory is low, the operating

 

system can trigger an event. The .NET runtime hooks into this event

 

to trigger garbage collection to help restore memory.

•\

Threshold on the heap is passed. Every managed heap, there is a

 

managed heap per .NET process, has an acceptable threshold. This

 

threshold is dynamic and can change while the process is running.

 

Once the threshold is crossed, garbage collection is triggered.

•\

GC.Collect() is triggered. System.GC is a static wrapper around the

 

garbage collector. Its Collect method triggers garbage collection.

 

We can call this manually for testing purposes or in very specific

 

scenarios, but usually we do not need to worry about this

300