Garbage collection and UPROPERTY
Unreal's GC keeps an object alive only if it can reach it through references it knows about:
UPROPERTY members or AddReferencedObjects. A raw pointer without UPROPERTY is
invisible. The object gets collected on the next GC pass, the pointer dangles, and you crash minutes later somewhere
unrelated. UPROPERTY is the fix, not TObjectPtr.
The question
A member UBodySetup* ClusterBodySetup; with no UPROPERTY, created with
NewObject<UBodySetup>(this), works in testing but crashes in physics a few minutes into a session. What's
happening? Give two fixes and when to choose each. Is TObjectPtr alone a fix?
What's happening
- GC is mark-and-sweep by reachability. It starts from the root set (rooted objects, loaded levels and their actors), follows every reference it knows about, and destroys everything it didn't reach.
- It only knows reflected references:
UPROPERTYmembers, or references reported viaAddReferencedObjects. - Trap: being the Outer doesn't keep it alive.
NewObject<T>(this)sets naming and package path, but the object is still collectable if nothing references it. - Why "a few minutes": GC runs periodically (interval, memory pressure, level transitions), not constantly.
- Why a crash, not a clean failure: a raw pointer isn't cleared when its object dies. It dangles into freed or reused memory, and the crash happens at next use, far from the cause.
Fixes, and when to use each
| Fix | Use when |
|---|---|
UPROPERTY() TObjectPtr<T> X; | Default. The owner is a UObject. You also get serialization, editor visibility and reference replacement. |
FGCObject + AddReferencedObjects | The owner is not a UObject (Slate widget, toolkit, plain C++ class) but must keep a UObject alive. |
TStrongObjectPtr<T> | Same as above, for a single pointer. It's a convenience wrapper. |
AddToRoot() / RemoveFromRoot() | Rarely. It's global lifetime until un-rooted, so forgetting leaks it. For process-lifetime singletons. |
TWeakObjectPtr<T> | Not a keep-alive. It lets you safely detect the object is gone (Get() → null). For things you don't own. |
TObjectPtr vs raw pointer
TObjectPtris UE5's replacement for rawUObject*in UPROPERTY members. In the editor it adds access tracking and lazy loading. In shipping it's the same size and cost as a raw pointer.- Convention:
TObjectPtrfor header members; rawT*is fine for parameters and locals. - Does
TObjectPtralone fix it? No.UPROPERTYis what makes the GC see it.
UPROPERTY() AWarriorBaseCharacter* OwningCharacter; in the anim instance. The UPROPERTY()
with no specifiers exists purely so the GC tracks it. You know why it's there now.
Bugs that pass a quick test (know three by heart)
- Raw UObject pointer without UPROPERTY: fine until the first GC pass.
- Undo/redo: a property changed without
Modify()/FScopedTransactionworks, but breaks Ctrl+Z. - Save → reload: transient or non-UPROPERTY state looks right in the session and is lost on reload.
- Live Coding vs full restart: CDO and reinstancing differences hide bugs.
- Editor vs packaged: code touching
WITH_EDITORONLY_DATAworks in-editor and fails in a build.
"The GC only follows reflected references, so a raw pointer without UPROPERTY doesn't keep it alive, and being its Outer doesn't either. The first GC pass collects it, the pointer dangles, physics crashes on next use. Fix: UPROPERTY on a TObjectPtr if the owner is a UObject; FGCObject or TStrongObjectPtr if it isn't. AddToRoot only for global lifetime. TObjectPtr alone doesn't fix it. UPROPERTY is what the GC sees."
Quiz yourself
How does Unreal's GC decide what to destroy?
Mark-and-sweep: from the root set, follow every reflected reference (UPROPERTY / AddReferencedObjects), mark what's reached, destroy the rest.
Does being an object's Outer keep it alive?
No. Outer controls naming and package, not reachability.
Why does the crash happen minutes later, somewhere else?
GC runs periodically, so the object survives short tests. After it's collected, the raw pointer still holds the old address, and the crash is wherever it's next used.
Is TObjectPtr<T> without UPROPERTY safe?
No. UPROPERTY is what makes the reference visible to the GC.
A Slate widget (not a UObject) needs to keep a material alive. What do you use?
FGCObject with AddReferencedObjects, or a TStrongObjectPtr.
Does TWeakObjectPtr keep things alive? What's it for?
No. It's for referring to something you don't own: it returns null once the object is gone, instead of dangling.
Name three bugs that pass a quick test in Unreal.
Any three: missing UPROPERTY (GC), missing Modify/transaction (undo), transient state (save/reload), live coding vs restart, editor-only code in a packaged build.