언리얼 엔진의 UObject 계열 객체는 일반 C++ 객체와 다르게 언리얼 GC (Garbage Collection)의 관리를 받는다.

객체 종류 생성 방식
UObject NewObject<T>()
AActor GetWorld()->SpawnActor<T>()
UActorComponent CreateDefaultSubobject<T>(), NewObject<T>()
UUserWidget CreateWidget<T>()

이 함수들은 객체를 생성하면서 언리얼 객체 시스템에 등록한다.
이후 GC는 등록된 객체들을 대상으로 생존 여부를 판단한다.


Strong Reference

UPROPERTY()가 붙은 UObject 참조는 GC가 추적할 수 있는 참조가 된다.

UPROPERTY()
TObjectPtr<UMyObject> MyObject;

MyObject가 어떤 객체를 가리키고 있고, 그 참조를 가진 객체가 살아 있다면 GC는 이 연결을 따라간다.

상태 GC 판단
루트에서 객체까지 UPROPERTY 경로가 있음 살아있는 객체
루트에서 객체까지 도달할 수 없음 수거 대상
일반 raw pointer만 존재 GC가 참조로 인식하지 못함

UPROPERTY는 GC가 따라갈 수 있는 참조 경로를 만든다.

UCLASS()
class UMyOwner : public UObject
{
    GENERATED_BODY()

public:
    UPROPERTY()
    TObjectPtr<UMyObject> ReferencedObject;
};

위 구조에서 UMyOwner가 살아 있고, ReferencedObjectUPROPERTY로 연결되어 있다면 ReferencedObject도 도달 가능한 객체로 판단된다.


자동 nullptr 처리

UPROPERTY로 등록된 UObject 포인터는 참조 대상 객체가 GC에 의해 제거될 때 자동으로 nullptr로 정리될 수 있다.

다만 Actor의 경우 Destroy() 호출 직후 바로 메모리에서 삭제되는 것이 아니라 Pending Kill / Destroyed 상태가 될 수 있다.
따라서 Actor나 UObject 유효성 확인에는 단순 if (Ptr)보다 IsValid(Ptr)이 더 안전하다.

if (IsValid(MyActorPtr))
{
    MyActorPtr->SomeFunction();
}
검사 방식 의미
if (Ptr) nullptr 여부만 확인
IsValid(Ptr) nullptr + 삭제 대기 상태까지 확인

UHT와 UPROPERTY 추적 정보

UPROPERTY()가 GC와 연결될 수 있는 이유는 UHT (UnrealHeaderTool) 때문이다.
컴파일 과정에서 UHT는 UPROPERTY()가 붙은 멤버를 분석하고, 리플렉션 정보를 생성한다.

UCLASS()
class AMyPlayer : public APawn
{
    GENERATED_BODY()

    UPROPERTY()
    TObjectPtr<AMyActor> MyActorPtr;
};

UHT는 대략 다음 정보를 생성한다.

정보 설명
클래스 이름 AMyPlayer
프로퍼티 이름 MyActorPtr
프로퍼티 타입 AMyActor 참조
메모리 위치 객체 내부에서 해당 멤버가 있는 오프셋
GC 추적 여부 UObject 참조로 추적 가능

GC는 런타임에 이 정보를 이용해 객체 내부의 UPROPERTY 포인터를 찾는다.


UObject 생성과 GC 등록

GC가 관리하려면 객체가 언리얼 객체 시스템에 등록되어야 한다.

UMyObject* Obj = NewObject<UMyObject>();
AMyActor* Actor = GetWorld()->SpawnActor<AMyActor>();
생성 방식 GC 관리
NewObject<T>() 관리됨
SpawnActor<T>() 관리됨
CreateWidget<T>() 관리됨
일반 new UObject 대상으로 사용하면 안 됨

UObject 계열을 일반 new로 생성하면 언리얼 객체 시스템, 리플렉션, GC와 정상적으로 연결되지 않는다.


Root Set

GC는 절대 삭제되면 안 되는 객체인 Root Set을 출발점으로 삼는다.

Root Set 예시 설명
UGameInstance 게임 실행 동안 유지
UWorld 현재 월드
엔진 핵심 객체 엔진이 직접 관리하는 객체
AddToRoot() 된 객체 개발자가 명시적으로 루트에 추가한 객체

Root Set에 있는 객체는 GC 탐색의 시작점이 된다.

MyObject->AddToRoot();

AddToRoot()를 사용하면 해당 객체가 Root Set 등록되어 엔진이 절대 삭제하지 않는다.
RemoveFromRoot()로 직접 제거하지 않으면 계속 살아 있기 때문에 남용하면 메모리 누수가 발생한다.


Reachability

GC는 Root Set에서 시작해서 UPROPERTY 참조를 따라갔을 때 해당 객체에 도달할 수 있는지를 본다.

판단 기준 결과
Root Set에서 도달 가능 살아있는 객체
Root Set에서 도달 불가능 Garbage
일반 포인터로만 참조 도달 경로로 인정되지 않음
flowchart LR
    A[Root Set] --> B[UWorld]
    B --> C[Actor]
    C --> D[D: UPROPERTY 참조 UObject]
    E[E : 일반 포인터만 있는 UObject]

D는 Root Set에서 UPROPERTY를 통해 도달 가능하므로 살아 있다.
E는 일반 포인터로만 참조되고 있다면 GC가 도달 가능한 객체로 보지 못할 수 있다.


Mark and Sweep

언리얼 GC는 기본적으로 Mark and Sweep 방식으로 동작한다.

Mark

Root Set에서 시작해 살아있는 객체를 표시한다.

flowchart TD
    A[Root Set] --> B[GameInstance]
    A --> C[World]
    C --> D[Actor]
    D --> E[UPROPERTY Object]
    E --> F[UPROPERTY Object]
단계 설명
1 Root Set 객체를 살아있음으로 표시
2 Root 객체의 UPROPERTY 참조를 탐색
3 참조된 UObject를 살아있음으로 표시
4 이 과정을 재귀적으로 반복

Sweep

Mark 단계에서 살아있음 표시를 받지 못한 객체를 정리한다.

단계 설명
1 등록된 모든 UObject 목록을 순회
2 Mark 되지 않은 객체를 Garbage로 판단
3 해당 객체를 참조하는 UPROPERTY 포인터 정리
4 객체 파괴 절차 진행
5 메모리 해제

객체 파괴 과정에서는 BeginDestroy(), FinishDestroy() 같은 단계가 관여한다.

virtual void BeginDestroy() override;

일반 게임플레이 로직에서 직접 호출하는 함수는 아니다.
객체 해제 과정에서 엔진이 호출한다.


TObjectPtr

UE5에서는 UObject 멤버 포인터에 TObjectPtr<T> 사용이 권장된다.

UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComponent;
구분 설명
UObject* 기존 raw UObject 포인터
TObjectPtr<T> UE5에서 권장되는 UObject 포인터 래퍼
UPROPERTY() TObjectPtr<T> GC 추적 가능한 안전한 멤버 참조

Actor Destroy와 GC

AActor::Destroy()는 Actor를 월드에서 제거 대상으로 표시하고, 엔진의 라이프사이클에 따라 정리되도록 만든다.

Destroy() 이후에는 포인터가 아직 nullptr이 아닐 수 있다.
하지만 객체는 더 이상 유효한 게임플레이 대상으로 보기 어렵다.
그렇기 때문에 IsValid()로 검사하는 습관이 안전하다.

상황 if (Ptr) IsValid(Ptr)
정상 객체 true true
nullptr false false
Destroy된 Actor true일 수 있음 false
GC 후 null 처리 false false

UPROPERTY와 메모리 누수

UPROPERTY는 GC가 참조 관계를 정확히 파악하도록 해준다.

UPROPERTY()
TObjectPtr<UMyObject> MyObject;

이 참조가 남아 있는 동안 MyObject는 도달 가능한 객체가 된다.
반대로 더 이상 필요 없다면 참조를 끊어야 GC가 수거할 수 있다.

MyObject = nullptr;
상태 결과
UPROPERTY 참조 유지 객체가 살아남을 수 있음
참조를 nullptr로 변경 다음 GC에서 수거 가능
AddToRoot() 후 해제 안 함 계속 살아남음
순환 참조 구조에 따라 수거 지연 또는 유지 가능

GC가 있다고 해서 설계 없이 참조를 계속 붙잡아도 되는 것은 아니다.
불필요한 강한 참조는 객체를 계속 살려둘 수 있다.


흐름 요약

flowchart TD
    A[NewObject / SpawnActor] --> B[UObject 시스템에 등록]
    B --> C[UPROPERTY 참조 설정]
    C --> D[GC 실행]
    D --> E[Root Set에서 탐색 시작]
    E --> F[UPROPERTY 참조를 따라 Mark]
    F --> G{도달 가능한가?}
    G -- Yes --> H[살아있는 객체로 유지]
    G -- No --> I[Garbage로 판단]
    I --> J[UPROPERTY 참조 null 처리]
    J --> K[BeginDestroy / FinishDestroy]
    K --> L[메모리 해제]

최종 요약

상황 권장
UObject를 멤버로 보관 UPROPERTY() TObjectPtr<T> 사용
Actor 참조 사용 전 검사 IsValid() 사용
UObject를 직접 생성 NewObject<T>() 사용
Actor 생성 SpawnActor<T>() 사용
컴포넌트 기본 생성 CreateDefaultSubobject<T>() 사용
필요 없어진 강한 참조 nullptr로 끊기
AddToRoot() 사용 반드시 RemoveFromRoot() 짝 맞추기
개념 요약
GC UObject의 생존 여부를 자동으로 판단하고 정리하는 시스템
UPROPERTY GC가 멤버 참조를 추적할 수 있게 만드는 리플렉션 정보
Root Set GC 탐색의 시작점이 되는 항상 살아있는 객체 목록
Reachability Root Set에서 UPROPERTY 경로로 도달 가능한지 여부
Mark 살아있는 객체를 표시하는 단계
Sweep 표시되지 않은 객체를 정리하는 단계
TObjectPtr UE5에서 권장되는 UObject 멤버 포인터 래퍼
IsValid nullptr와 삭제 대기 상태를 함께 확인하는 유효성 검사
반응형

'Unreal Engine' 카테고리의 다른 글

AssetManager ⇄ Bundle System 구조 수정  (0) 2026.07.13
AssetManager ⇄ Bundle System(Data Asset)  (0) 2026.07.09
Unreal Engine Singleton  (0) 2026.07.07
Unreal Engine Tick 최적화  (0) 2026.07.06
UE 애니메이션 리타게팅  (0) 2026.07.03