In this article we will talk about the interaction of the IL2CPP runtime environment with the garbage collector and see how the roots of the garbage collector in managed code are associated with the native garbage collector.

Previous topic materials:
IL2CPP: P / Invoke wrappers for types and methodsIL2CPP: method calls')
In this article, as in the previous publications of the series, we will reveal the details of the implementation of a separate component of IL2CPP, which may be changed in the future. Consider some internal APIs that the runtime code uses to interact with the garbage collector. These APIs are not public, so you should not try to call them from the code of any real project.
Garbage collection
I will not give here general information about garbage collection, as this is a fairly broad topic, which is devoted to a lot of research and publications. For brevity, we will present the garbage collector in the form of an algorithm that builds directed graphs from references to objects. If the Child object is used by the Parent object with a pointer in the native code, the graph will look like this:

The garbage collector is looking for objects without a parent object in the memory used by the process. If such an object is found, then the memory occupied by it can be freed and reused for another purpose.
Of course, most objects must have some kind of parent object. Therefore, the garbage collector needs to be able to distinguish particular parent objects. I prefer to think of the latter as objects used by the program. In the terminology of the garbage collector, they are called "roots." Below is an example of a parent object without a root.

In this case, the Parent 2 object has no root, so the garbage collector can reuse the memory occupied by Parent 2 and Child 2. In turn, Parent 1 and Child 1 have a root - which means they are used by the program, and the garbage collector will not be reused use their memory, since the program still uses them for a specific purpose.
In .NET, three types of roots are used:
- local variables on the stack of any thread executing managed code;
- static variables;
- GCHandle objects .
We will look at the IL2CPP relationship with the garbage collector when dealing with the roots of all the above species.
Preparation for work
I work in Unity version 5.1.0p1 on OSX, and I will build for the iOS platform. This will allow us to use Xcode to monitor the interaction of the IL2CPP with the garbage collector. As in the previous examples, we will use a project containing one script:
using System; using System.Runtime.InteropServices; using System.Threading; using UnityEngine; public class AnyClass {} public class HelloWorld : MonoBehaviour { private static AnyClass staticAnyClass = new AnyClass(); void Start () { var thread = new Thread(AnotherThread); thread.Start(); thread.Join(); var anyClassForGCHandle = new AnyClass(); var gcHandle = GCHandle.Alloc(anyClassForGCHandle); } private static void AnotherThread() { var anyClassLocal = new AnyClass(); } }
I checked the Development Build option in the Build Settings window, and selected Debug opposite Run in Xcode as. In the generated Xcode project, first find the string Start_m. You should see the generated code for the Start method of the HelloWorld class called HelloWorld_Start_m3.
Add stream local variables as roots
Add a breakpoint in the HelloWorld_Start_m3 function on the line where Thread_Start_m9 is called. This method creates a new managed stream that will be added as a root to the garbage collector. This process can be tracked in the libil2cpp header files that come with Unity. In the Unity installation directory, open the Contents / Frameworks / il2cpp / libil2cpp / gc / gc-internal.h file. It contains a number of methods with the il2cpp_gc_ prefix and is part of the API between the libil2cpp runtime and the garbage collector. But remember that this API is public, therefore these methods should not be called from the code of the real project. In addition, they may be changed in the new version without notice.
Add a breakpoint in function il2cpp_gc_register_thread in Xcode. To do this, select Debug> Breakpoints> Create Symbolic Breakpoint.

This point is reached almost instantly after the launch of the project in Xcode. In this case, we do not see the source code, since it is built in the static library of the libil2cpp environment, however, it is clear from the call stack that this thread is created in the InitializeScriptingBackend method, which is executed at startup.

We will see that this point will be reached several times as you create managed threads for internal use. For now, you can turn it off in Xcode and continue the project without it. We have to reach the breakpoint that was added earlier in the HelloWorld_Start_m3 method.
Now I'm going to run the managed thread created by our script code, so I need to re-enable the breakpoint on il2cpp_gc_register_thread. Having reached it, we will see that the first thread is waiting to join the created stream, but the call stack for the created stream shows that we are only launching it:

When a new thread communicates with the garbage collector, the latter interprets all objects in the local stack of this thread as roots. Take a look at the generated code for the HelloWorld_AnotherThread_m4 method:
AnyClass_t1 * L_0 = (AnyClass_t1 *)il2cpp_codegen_object_new (AnyClass_t1_il2cpp_TypeInfo_var); AnyClass__ctor_m0(L_0, NULL); V_0 = L_0;
We see one local variable L_0, which the garbage collector must interpret as the root. For a short time while this stream exists, this instance of the AnyClass object and any other objects it references cannot be reused by the garbage collector. Variables defined on the stack are the most common type of root, since the objects in the program mostly begin with a local variable in the method that is executed in the controlled stream.
At the end of the thread, the il2cpp_gc_unregister_thread function is called, which tells the garbage collector to no longer interpret thread stack objects as roots. After that, the garbage collector will be able to reuse the memory occupied by an object of the class AnyClass, which is represented in the native code as L_0.
Static variables
Some variables are not dependent on call flow streams. We are talking about static variables, and they must also be interpreted by the garbage collector as root.
When IL2CPP creates a native class mapping, all static fields are grouped into a C ++ structure that is different from the field instances in the class. Let's proceed to the definition of the HelloWorld_t2 class in Xcode:
struct  HelloWorld_t2  : public MonoBehaviour_t3 { }; struct HelloWorld_t2_StaticFields{
Note that the IL2CPP technology does not use the C ++ static keyword, since it must constantly monitor the placement of static fields, as well as the allocation of memory for them, in order to properly interact with the garbage collector. When a particular type is first used in a runtime environment, the libil2cpp code will initialize the type. Such initialization includes the allocation of memory for the HelloWorld_t2_StaticFields structure. Memory is allocated using a special call to the garbage collector: il2cpp_gc_alloc_fixed (you can see it in the gc-internal.h file).
After this call, the garbage collector will take the allocated memory by the root until the end of the process. You can set a breakpoint on the ilcode 2cpp_gc_alloc_fixed function in Xcode, but it is called quite often (even in a project as simple as ours), so it will not be very useful.
GCHandle Objects
In some cases it is undesirable to use static variables, but it is necessary to control exactly when the garbage collector can reuse the memory allocated for the object. For example, you need to transfer the pointer of the managed object from the managed code to the native one. If the native code gets the opportunity to dispose of this object, we need to inform the garbage collector that the native code is now the root in its object graph. To do this, use a special managed object GCHandle.
When creating a GCHandle object, the processing environment code begins to interpret the selected managed object as the root in the garbage collector to prevent the memory from either this object or any other to which it refers. In IL2CPP, we see how the low-level API does this in the Contents / Frameworks / il2cpp / libil2cpp / gc / GCHandle.h file. Again, I remind you that this API is not public. Add a breakpoint to the function GCHandle :: New. If we continue with the project, this call stack should appear:

The generated code for the Start method calls the GCHandle_Alloc_m11 method, which eventually creates a GCHandle object and notifies the garbage collector about the new root object.
Conclusion
The topic of integration of the garbage collector in IL2CPP is still far from exhausted. I highly recommend readers to independently study more material about the interaction of the IL2CPP and the garbage collector.