PART 1 · 강의 3/7

UE5 메모리 관리

Garbage Collection과 UPROPERTY로 안전한 메모리 관리하기

01

가비지 컬렉션 개요

언리얼 엔진의 자동 메모리 관리 시스템

언리얼 엔진은 UObject 기반 객체에 대해 자동 가비지 컬렉션(GC)을 제공합니다. C++의 수동 메모리 관리와 달리, GC가 참조되지 않는 객체를 자동으로 정리합니다.

🔥 핵심 규칙

UObject 참조는 반드시 UPROPERTY()로 마킹해야 GC가 추적할 수 있습니다!

GC 작동 방식 // GC 주기: 30-60초마다 자동 실행 // 1. Root Set에서 시작 (월드, 게임 인스턴스, 활성 레벨 등) // 2. UPROPERTY로 마킹된 참조를 따라 그래프 탐색 // 3. 참조되지 않는 객체는 정리 대상 /* +---------------+ | Root Set | +-------+-------+ | v UPROPERTY 참조 +-------+-------+ | UObject A | --> 살아있음 +-------+-------+ | v UPROPERTY 참조 +-------+-------+ | UObject B | --> 살아있음 +---------------+ +---------------+ | UObject C | --> UPROPERTY 없음 --> GC 대상! +---------------+ */
02

UPROPERTY와 GC

올바른 UObject 참조 방법

C++ UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: // ✅ 올바른 방법 - GC가 추적 가능 UPROPERTY() TObjectPtr<UMyComponent> MyComponent; // ❌ 잘못된 방법 - GC가 볼 수 없음, 댕글링 포인터 위험! UMyComponent* DangerousPointer; // 절대 금지! // ✅ 배열도 반드시 UPROPERTY UPROPERTY() TArray<TObjectPtr<UObject>> MyObjects; // ✅ Map도 UPROPERTY 필요 UPROPERTY() TMap<FName, TObjectPtr<UObject>> ObjectMap; };

함수 내 지역 포인터

C++ void AMyActor::MyFunction() { // ✅ 함수 스코프 내에서는 안전 // GC는 함수 실행 중에 실행되지 않음 UObject* LocalPtr = SomeFunction(); // 이 범위 안에서 LocalPtr 사용은 안전 LocalPtr->DoSomething(); // 함수 종료 후 LocalPtr은 무효화될 수 있음 } // ⚠️ 주의: 비동기 콜백에서는 다름! void AMyActor::StartAsyncWork() { UObject* Obj = GetSomeObject(); // ❌ 위험! 콜백 실행 전에 GC될 수 있음 AsyncTask(ENamedThreads::AnyThread, [Obj]() { Obj->DoSomething(); // 크래시 가능! }); }
💡 안전한 비동기 패턴

비동기 작업에서 UObject를 사용할 때는 TWeakObjectPtr를 사용하고, 사용 전에 유효성을 검사하세요.

03

객체 생성

NewObject, SpawnActor, CreateDefaultSubobject

C++ // ===== UObject 생성: NewObject ===== UMyObject* Obj = NewObject<UMyObject>(this); UMyObject* Obj2 = NewObject<UMyObject>(this, UMyObject::StaticClass()); // ===== Actor 생성: SpawnActor ===== FActorSpawnParameters SpawnParams; SpawnParams.Owner = this; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AMyActor* Actor = GetWorld()->SpawnActor<AMyActor>( AMyActor::StaticClass(), SpawnLocation, SpawnRotation, SpawnParams ); // ===== Component 생성: CreateDefaultSubobject (생성자에서만!) ===== AMyActor::AMyActor() { // 생성자에서만 사용 가능! MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh")); MeshComponent->SetupAttachment(RootComponent); } // ===== Runtime에서 Component 생성 ===== void AMyActor::AddComponentAtRuntime() { UStaticMeshComponent* NewComp = NewObject<UStaticMeshComponent>(this); NewComp->SetupAttachment(RootComponent); NewComp->RegisterComponent(); // 반드시 등록! }
⚠️ 절대 금지
// ❌ C++ new 사용 금지! UMyObject* Bad = new UMyObject(); // 절대 하지 마세요! // UObject는 반드시 NewObject, SpawnActor 등 사용
04

객체 파괴

Destroy, MarkPendingKill, GC 대기

C++ // ===== Actor 파괴 ===== void AMyActor::DestroyEnemy(AActor* Enemy) { if (Enemy && IsValid(Enemy)) { Enemy->Destroy(); // Destroy() 호출 후에도 포인터는 즉시 무효화되지 않음 // 다음 GC 사이클에서 실제로 정리됨 } } // ===== UObject는 Destroy가 없음 ===== void AMyActor::RemoveObject() { if (MyObject) { // UObject는 참조를 끊으면 GC가 처리 MyObject->MarkAsGarbage(); // UE5 MyObject = nullptr; // 또는 그냥 참조만 끊어도 됨 // MyObject = nullptr; } } // ===== IsValid로 안전하게 체크 ===== void AMyActor::SafeAccess() { // nullptr 체크와 PendingKill 체크를 동시에 if (IsValid(TargetActor)) { TargetActor->DoSomething(); } // TWeakObjectPtr 사용 시 if (WeakActorRef.IsValid()) { WeakActorRef->DoSomething(); } }

Actor::Destroy()

Actor를 파괴 대기 상태로 표시. EndPlay가 호출되고, 다음 GC에서 메모리 해제.

MarkAsGarbage()

UE5에서 UObject를 GC 대상으로 표시. 참조를 끊으면 자동으로 처리됨.

IsValid()

nullptr과 PendingKill을 동시에 체크. Actor/UObject 접근 전 항상 사용 권장.

05

GC 최적화

대규모 게임을 위한 GC 튜닝

DefaultEngine.ini ; GC 설정 [/Script/Engine.GarbageCollectionSettings] ; GC 실행 간격 (초) gc.TimeBetweenPurgingPendingKillObjects=30 ; Incremental GC (TObjectPtr 필수) gc.IncrementalBeginDestroyEnabled=1 gc.AllowIncrementalGC=1 ; 클러스터링으로 GC 최적화 gc.ActorClusteringEnabled=1 gc.BlueprintClusteringEnabled=1
수동 GC 제어 // 수동으로 GC 실행 (로딩 화면 등에서) GEngine->ForceGarbageCollection(true); // 특정 오브젝트를 GC 루트에 추가 (제거 방지) MyObject->AddToRoot(); // 루트에서 제거 (이제 GC 대상이 될 수 있음) MyObject->RemoveFromRoot(); // 클러스터링으로 관련 오브젝트 그룹화 UCLASS() class UMyClusteredObject : public UObject { GENERATED_BODY() public: virtual void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) override { Super::AddReferencedObjects(InThis, Collector); // 커스텀 참조 추가 } };
💡 대규모 월드 팁

World Partition과 함께 스트리밍되는 Actor들은 자동으로 클러스터링됩니다. TObjectPtr를 사용하면 Incremental GC가 활성화되어 GC 히칭을 줄일 수 있습니다.

SUMMARY

핵심 요약

  • UPROPERTY 필수 — UObject 참조는 반드시 UPROPERTY()로 마킹해야 GC가 추적
  • NewObject/SpawnActor — C++ new 대신 언리얼 팩토리 함수 사용
  • CreateDefaultSubobject — 생성자에서만 컴포넌트 생성에 사용
  • IsValid() 체크 — nullptr과 PendingKill을 동시에 검사
  • TObjectPtr — UE5에서 Incremental GC 활성화의 핵심
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: UPROPERTY 기반 안전한 참조 관리

ARPGCharacter 클래스에서 모든 UObject 참조를 UPROPERTY()와 TObjectPtr<>로 선언하세요. TArray>로 인벤토리 아이템 배열을, TMap>로 장비 슬롯을 구현하세요.

실습 2: 객체 생성 패턴 연습

NewObject<>로 UItemData를 생성하고, SpawnActor<>로 ADroppedItem을 월드에 스폰하고, CreateDefaultSubobject<>로 UInventoryComponent를 생성하세요. 각 패턴의 사용 시점을 확인하세요.

심화 과제: GC 최적화 및 Incremental GC 설정

DefaultEngine.ini에서 gc.TimeBetweenPurgingPendingKillObjects, gc.AllowIncrementalGC 설정을 조정하고, ForceGarbageCollection()과 stat gc 명령으로 GC 성능을 프로파일링하세요. 대량 몬스터 스폰/파괴 시나리오에서 GC 히칭을 측정하세요.