Hello, Great and terrible habrazhiteli!
As I recently learned, not many people know how the garbage collector works. Although I understand that 99% of developers do not really need this, but I would like everyone who develops applications on .NET to know how it works. In this article I will try to briefly describe how
the garbage collector actually works.
Basic information about the lifetime of the object
As you know, when initializing an object in memory, it allocates the right place for an object. Using the
new keyword results in adding a class object to the so-called
managed heap , and a reference to the object is returned.
When creating C # applications, you can safely assume that the .NET runtime will take care of the
management heap itself without direct intervention from the programmer. In fact, the “
golden rule” of memory management is:
Place the object in the control heap using the new keyword and forget about it.
After creation, the object will be automatically deleted by the garbage collector when it is no longer needed. The question immediately arises of how the garbage collector determines when the object is not needed?
Let's look at a simple example:
public static void MakeACar() {
')
Note that the reference to the Car object (myCar) was created directly inside the MakeACar () method and was not passed outside of the defining scope (neither as a return value, nor as a ref / out parameter). Therefore, after calling the method, the reference to myCar will be unreachable, and the Car object will be a candidate for removal
by the garbage collector . However, it should be understood that there is no guarantee that the object will be deleted immediately after the execution of the MakeACar () method. All that can be assumed at the moment is that when the garbage collection is performed in the CLR environment the next time, the myCar object will be
set for deletion.
C ++ programmers are well aware that unless they deliberately take care of deleting objects located in a heap,
memory leaks will soon appear. In fact, tracking down memory leaks is one of the most tedious and lengthy aspects of programming in unmanaged environments.
Role of application root elements
To figure out how the garbage collector is determined when the object is no longer needed, you need to know what the
application application root elements are . Simply put, a
root element is a cell in memory that contains a reference to a heaped object. Strictly speaking, the root elements can be called:
- Links to global objects (although they are not allowed in C #, the CIL code allows you to place global objects
- Links to any static objects or static fields.
- References to local objects within the application code base.
- References to the object parameters passed to the method.
- References to the object awaiting finalization .
- Any CPU registers that reference the object.
During garbage collection processes, the runtime will examine the objects on the heap to determine if they are still reachable (i.e., root) to the application. To do this, the CLR will create
object graphs representing all the objects reachable by the application. In addition, it should be borne in mind that the garbage collector will never create a graph for the same object twice, avoiding the need to perform cyclic reference counting, which is typical for programming in the COM environment.
Object Generations
When trying to detect unreachable code, CLR environment objects
do not literally check every object in the heap . Obviously, this would take a lot of time, especially in large projects.
To optimize the process, each object in the heap refers to a certain
“generation”. The meaning in the use of generations looks quite simple:
The longer the object is in the heap, the higher the likelihood that it will remain there.
For example, a class defined in the main window of a desktop application will remain in memory until the end of the program. On the other hand, an object that has recently fallen into a heap (for example, those that are in the scope of methods) will most likely become unattainable quickly enough. Worryingly of these assumptions, every object in the heap refers to:
- Generation 0. Identifies a new, just-placed object that has never been marked as proper removal during garbage collection.
- Generation 1. Identifies an object that has already “survived” one garbage collection process (was marked as being properly deleted, but was not deleted due to sufficient free space on the heap).
- Generation 2. Identifies an object that has experienced more than one garbage collection run.
Generations 0 and 1 are called
ephemeral .
The garbage collector first analyzes all objects that belong to generation 0. If after their removal there is a sufficient amount of memory, the status of all the surviving objects rises to generation 1. If all objects of generation 0 were checked, but additional space is still required, it will be checked generation objects 1. Objects of this generation, which managed to survive, will become objects of generation 2. if the garbage collector
still needs memory, then objects of generation 2 will undergo garbage collection. Since There are no objects above the 2nd generation, the status of the objects will not change.
From the above, we can conclude that newer objects will be deleted faster than older ones.
Parallel garbage collection in .NET 1.0 - .NET 3.5
Before the release of .NET 4.0, the cleaning of unused objects was carried out using the technique of
parallel garbage collection . In this model, when performing garbage collection of
ephemeral objects, the garbage collector temporarily suspended all active
threads within the current process, so that the application could not access the managed heap until the garbage collection process was completed.
At the end of the garbage collection cycle, suspended threads were allowed to continue working again. Fortunately, in .NET 3.5 the garbage collector was well optimized and therefore the associated short interruptions in working with the application rarely became noticeable.
Like optimization, parallel garbage collection allowed cleaning of objects that were not found in any ephemeral generation in a separate stream. This reduced (but did not eliminate) the need to suspend active threads by the .NET runtime. Moreover, parallel garbage collection made it possible to place objects on the heap during the assembly of objects of non-ephemeral generations.
Background garbage collection in .NET 4.0
And finally, I would like to tell you about improving the work of the garbage collector in .NET 4.0.
In .NET 4.0, the garbage collector decides in a different way about suspending threads and cleaning objects in a managed heap, using the
background garbage collection technique. Despite its name, this does not mean that all garbage collection now occurs in additional background threads. In fact, in the case of background garbage collection for non-ephemeral generation objects, the .NET runtime can now collect garbage from ephemeral generation objects in a separate background thread.
The garbage collection mechanism in .NET 4.0 has been improved so that it takes less time to suspend the flow associated with garbage collection details. Thanks to these changes, the process of cleaning unused objects of generation 0 and 1 has become optimal. It allows you to get a higher level of application performance.
Conclusion
In conclusion, I would like to say that the garbage collector that is used in .NET 4.0 allows you to optimize the performance of programs and has virtually no effect on performance.
In the next article I will explain how you can personally manage the garbage collection process using the System.G namespace.
More detail:
here