Hit enter to search or ESC to close

Garbage Collection in UE4

Kazimieras

If you are a bit like me, in learning to program for Unreal Engine 4, you have eventually bumped into the mighty garbage collector (GC). For some, this first encounter is agreeable – it’s a relief that GC looks after us and prevents memory leaks by design. For others, rubbing shoulders with the garbage collector isn’t that great. If we do not play nice with its rules, it will not play nice with our objects. This post is all about running through the basics of being on the right side of the garbage collector.

What is and isn’t Collected

Before we dive into some concrete examples, let’s familiarize ourselves with the core design of the Unreal Engine’s garbage collector. The principal idea is straightforward – there is a small number of conditions which all have to be met for Unreal to believe the object is redundant and delete it:

  1. The object must no longer be referenced in the Unreal Engine’s reflection system using property declarations,
  2. A pointer to the object must not be stored in a container class,
  3. The object must not have strong pointer types such as shared pointers, shared references, unique pointers (or just plainly strong pointers) pointing to it, and in scope.
  4. The code block in which the object was created must have concluded executing,
  5. The object is not a part of the root set of the GC graph (more on this below),
  6. The object class is derived from UObject.

There is also one big exception to this concept – we can always explicitly mark UObject-derived class objects for destruction. But that’s pretty much it.

Advanced Implications

Even though the core design is simple, there are some implications that might not be immediately obvious. These are the common gotcha’s:

  • Actors and actor components in a world (level) will be referenced by their parent object, such as the level itself. Spawning an actor in the level from anywhere, therefore, does not require special GC considerations.
  • When a new level or map is loaded, the engine will destroy the world and create a new one. Everything within the old world will be garbage collected, including the game mode, the game state, and everything they reference/point to (assuming that is the sole reference or pointer to an object).
  • Not all pointers to invalid objects will equal nullptr. If an object is marked for destruction or if it hasn’t been initialized correctly, its nullable pointer might not nullptr. Make it a habit to check objects with IsValid.
  • Any object class (or primitive type for that matter) that is not derived from the UObject class will not be considered for garbage collection. This alone is a very good reason to derive all classes we make from existing UE game classes, or at the very least – UObject.

Garbage Collection Graph

So far, we have been talking about the rules and principles of garbage collection in Unreal. But how is it really, technically done?

The engine maintains a garbage collection graph of objects. At the very root of the graph are objects that will never be garbage collected, collectively known as the root set. These should be the common parents, grandparents, and so on, for the entire game.

Whenever it is necessary to GC, the engine will start with the root set of objects and see what they are referencing and pointing to through the reflection system or container classes. Whatever is being pointed to or referenced will be added to the “untouchable” list – the graph. Then it will examine what the newly added objects point to or reference, and it will add all of that to the graph as well. Moving along the graph this way, eventually, the garbage collection system builds a list of all untouchable objects, and will just delete everything else.

Here’s a handy illustration of a garbage collection graph:

As you can see, if an object can be traced back to the root set through the engine’s property system’s pointers, it will not be garbage collected. Once these ties to the root set are severed, the object will be swept up, as seen on the right side of the illustration.

Seeing this “parent” – “child” relationship in the graph, one might reasonably think that there must be some logical overlap with Unreal Engine’s outer classes. When a new object is created, we have an opportunity to specify which object is its parent, or whether our new object should be in the transient package. These relationships are not related to the garbage collection graph; it is crucial to know this distinction.

Slate UI

A huge exception to all rules with garbage collection in the Unreal Engine is the Slate user interface system. As of 4.25, it does not use strong pointers to hold on to the objects (frequently assets) that are used in the user interface. It means that GC will freely delete all objects no longer referenced anywhere except for in the Slate UI. Also, when an object it uses is garbage collected, the Slate UI can crash the game.

To mitigate this, make sure that every object that you pass to the Slate UI is already firmly protected from garbage collection. If necessary, one may use explicitly strong pointers, but normally this would be done through the property system. For example, one may create a new object in the root set of the garbage collection graph, and have it hold an array of other objects that correspond to Slate UI widgets. These objects would point to resource objects that the Slate UI uses. Each of the objects dedicated to their own Slate UI widgets could be destroyed once the widget is.

Debugging

Sometimes we have an issue in our code that could be caused by garbage collection, but we aren’t sure yet. It may very well be an object initialization problem, or perhaps a plethora of other things.

We may test whether an object is valid at any stage of our code execution using an assertion:

checkf(IsValid(Object), TEXT("Uh oh! A necessary object is invalid."));

Or, if we are debugging our game anyways and just want to trigger a breakpoint, we may use a different style of assertion:

check(IsValid(Object));

Good Practice

Now that we have considered the garbage collection system in some depth, let’s look at some examples of frequently used idioms that get along with Unreal Engine’s GC well.

The first great idiom is using pointers declared with UPROPERTY() and in container classes like TArray. An object pointed to this way to any object in the garbage collection graph will be assumed necessary. This is by far the most common way C++ programmers signal to Unreal Engine that their objects are essential. Once no “parent” object points to “child” object this way anymore, only then the “child” can be destroyed.

// Part of a class declaration in a header file
UPROPERTY()
UMyCustomClass * MyPointer;

UPROPERTY()
UTexture2D * MyTexture;

UPROPERTY()
TArray<UStaticMesh*> MyArrayOfMeshes;

We can freely point to the same object from multiple other objects. As long as at least one alive object points to it using the property system, it will not be garbage collected.

On the complete opposite side of the gamut is another good idiom – temporary objects. They are intended to be garbage collected after the scope concludes executing. Below is a piece of functional code that temporarily creates a new object to capture a picture from a scene capture component and store it into an array of pixel colors. After this scope ends, the TextureRenderTarget itself will be duly swept up by GC.

// Part of a function definition in an ASceneCapture2D-derived class
const uint16 Resolution = 512;
UTextureRenderTarget2D * TextureRenderTarget = NewObject<UTextureRenderTarget2D >();
USceneCaptureComponent2D* SCC2D = GetCaptureComponent2D();

TextureRenderTarget->InitCustomFormat(Resolution,Resolution,PF_B8G8R8A8,false);
SCC2D->TextureTarget = TextureRenderTarget;
SCC2D->CaptureScene();

TArray<FColor> RawPixels;
RawPixels.Reserve(Resolution * Resolution);
TextureRenderTarget->GameThread_GetRenderTargetResource()->ReadPixels(RawPixels);
RawPixels.Shrink();

Bad Practice

There are two main types of frowned upon abuse of the Unreal Engine’s garbage collection system. One is placing all objects in the root set of the graph, and the other is creating objects that do not adhere to the six rules stated above, but they are used quickly enough so that the garbage collector generally (but not always!) doesn’t collect them in time to cause an exception.

Frequently programmers who are a bit annoyed with the garbage collector will create all of their objects in the root set. This is not a great practice because it discards all utility of the garbage collector – those who do this must manage their memory and delete their objects manually. Remember, being in the root set was rule #5.

Sometimes you might see code where an object is created with a new pointer within the scope of a function definition, and its pointer is passed around somewhere to carry out a quick operation on it. It may work sometimes, or even most of the time. Eventually, that will crash the game, and if that’s after shipping, this will be a pain to debug.

Aside from the two bad practices that we encounter quite a bit, in my personal opinion, there is a third one – over-reliance on nullptr. Code like

if(ObjectPointer) {
...
}

looks very convenient and easy to read (no doubt), but ObjectPointer will evaluate to true even if the object is uninitialized or marked for kill. It’s better to use the value of IsValid(ObjectPointer) if we intend to use the object.

Conclusion

In summation, the garbage collection system is a robust part of Unreal Engine that affords C++ programmers a lot of safety from memory leaks, as well as convenience. With this high-level discussion, I was aiming to introduce the system at a conceptual level, and I hope I have achieved that.

If you’d like to read more about the Garbage Collection in Unreal Engine, I suggest browsing the Unreal Engine Architecture Documentation as well as Unreal Engine Scripting with C++ Cookbook by William Sherif and Stephen Whittle. The latter recommendation will have a less conceptual overview with a focus on code examples.

Do you have any thoughts or insights about Garbage Collection in Unreal Engine? Let me know in the comments!


Leave a Reply

Your email address will not be published.Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.