Understanding Garbage Collection in UE5 C++
Practical guide to how Unreal's UObject GC works, common pitfalls, and C++ patterns that keep your game stable and performant.
Overview
Unreal Engine manages memory for engine-managed objects (UObjects) with a built-in garbage collector (GC). Unlike C++ RAII or smart-pointer ownership, UE's GC performs reachability analysis to decide which UObjects are still in use and which can be reclaimed. This article explains the core concepts you need when writing UE5 C++ so you avoid leaks, hitches, and dangling references.
How Unreal's Garbage Collector Works
Mark-and-sweep model
UE uses a mark-and-sweep GC for UObjects: the collector marks reachable objects (roots and anything they reference) and then sweeps away unmarked objects. The marking phase performs reachability analysis across UObject references discovered via the reflection system. This design is deterministic for UObject lifetimes but can cause frame stalls if many objects must be scanned in one frame.
Roots and reachability
Roots include objects referenced from global structures, objects on the stack that the engine knows about, objects added to the root set (e.g., via AddToRoot()), and any references discovered through UPROPERTY metadata. Anything not reachable from the root set is eligible for collection.
UObject lifecycle and reflection
UObjects are created with engine factories (for example, NewObject<T>() or ConstructObject) and are tracked by the GC. The reflection system (UClass, UProperty metadata) is how the GC discovers references between UObjects at runtime; only properties exposed to reflection (typically marked with UPROPERTY) are automatically scanned. Non-reflected raw pointers are invisible to the GC and can lead to premature collection or dangling pointers if not handled carefully.
Common lifecycle operations
- Create — use
NewObject<T>()or factory functions. - Protect — add to root or keep a UPROPERTY reference to prevent collection.
- Destroy — call
ConditionalBeginDestroy()indirectly via engine flows; GC will reclaim when unreachable.
References, UPROPERTY, and smart pointers
Understanding which references the GC sees is the single most important concept for safe UE C++ code. Use the reflection system and engine-provided pointer types to express ownership and lifetime correctly.
UPROPERTY
Mark UObject pointers with UPROPERTY() so the GC can find them. Without UPROPERTY, the GC treats the pointer as invisible and may collect the object while your code still holds a raw pointer. Example:
UPROPERTY()
AActor* MyActor;
Pointer types
- TStrongObjectPtr / UPROPERTY — tracked by GC; use for owning references.
- TWeakObjectPtr — non-owning, safe to hold; becomes null if object is destroyed.
- TSharedPtr / std::shared_ptr — not tracked by UE GC; use only for non-UObject data.
- FGCObject — implement when you need to hold UObject references from non-UObject classes so the GC can be informed.
Example: safe non-owning reference
UPROPERTY()
TWeakObjectPtr<AActor> TargetActor;
if (TargetActor.IsValid())
{
TargetActor->DoSomething();
}
Best practices for UE5 C++
- Always use UPROPERTY for UObject members that must keep the object alive or be visible to the editor/replication.
- Prefer TWeakObjectPtr for caches or observers to avoid accidental ownership and dangling raw pointers.
- Use AddToRoot sparingly — it prevents GC entirely for that object until removed and can cause leaks if misused. - Avoid mixing UE GC and external smart pointers for the same UObject; choose one ownership model per object type.
- Implement FGCObject when non-UObject systems need to hold UObjects across GC cycles.
- Profile GC pauses and consider incremental GC options for large object graphs or streaming-heavy games.
Tradeoffs
Using UPROPERTY everywhere is safe but increases the amount of data the GC must scan. Minimizing reflected references where possible reduces GC work but requires careful manual lifetime management. Incremental GC reduces pause risk at the cost of implementation complexity and engine version constraints.
Debugging and profiling GC issues
Key tools and techniques:
- GC logging — enable engine GC logs to see what objects are being marked and swept.
- Reference viewers — use the editor's reference viewer to inspect why an object is still reachable.
- Memory snapshots — capture and compare snapshots to find leaks or unexpected roots.
- Use TWeakObjectPtr checks and defensive nulling to avoid dereferencing collected objects.
When investigating hitches, measure how long the marking phase takes and which object sets dominate the scan; that will point to which UPROPERTY graphs to simplify or make non-reflected.