UE5 메모리 관리
Garbage Collection과 UPROPERTY로 안전한 메모리 관리하기
가비지 컬렉션 개요
언리얼 엔진의 자동 메모리 관리 시스템
언리얼 엔진은 UObject 기반 객체에 대해 자동 가비지 컬렉션(GC)을 제공합니다. C++의 수동 메모리 관리와 달리, GC가 참조되지 않는 객체를 자동으로 정리합니다.
UObject 참조는 반드시 UPROPERTY()로 마킹해야 GC가 추적할 수 있습니다!
// GC 주기: 30-60초마다 자동 실행
// 1. Root Set에서 시작 (월드, 게임 인스턴스, 활성 레벨 등)
// 2. UPROPERTY로 마킹된 참조를 따라 그래프 탐색
// 3. 참조되지 않는 객체는 정리 대상
/*
+---------------+
| Root Set |
+-------+-------+
|
v UPROPERTY 참조
+-------+-------+
| UObject A | --> 살아있음
+-------+-------+
|
v UPROPERTY 참조
+-------+-------+
| UObject B | --> 살아있음
+---------------+
+---------------+
| UObject C | --> UPROPERTY 없음 --> GC 대상!
+---------------+
*/
UPROPERTY와 GC
올바른 UObject 참조 방법
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;
};
함수 내 지역 포인터
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를 사용하고, 사용 전에 유효성을 검사하세요.
객체 생성
NewObject, SpawnActor, CreateDefaultSubobject
// ===== 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 등 사용
객체 파괴
Destroy, MarkPendingKill, GC 대기
// ===== 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 접근 전 항상 사용 권장.
GC 최적화
대규모 게임을 위한 GC 튜닝
; 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 실행 (로딩 화면 등에서)
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 히칭을 줄일 수 있습니다.
핵심 요약
- UPROPERTY 필수 — UObject 참조는 반드시 UPROPERTY()로 마킹해야 GC가 추적
- NewObject/SpawnActor — C++ new 대신 언리얼 팩토리 함수 사용
- CreateDefaultSubobject — 생성자에서만 컴포넌트 생성에 사용
- IsValid() 체크 — nullptr과 PendingKill을 동시에 검사
- TObjectPtr — UE5에서 Incremental GC 활성화의 핵심
도전 과제
배운 내용을 직접 실습해보세요
ARPGCharacter 클래스에서 모든 UObject 참조를 UPROPERTY()와 TObjectPtr<>로 선언하세요. TArray
NewObject<>로 UItemData를 생성하고, SpawnActor<>로 ADroppedItem을 월드에 스폰하고, CreateDefaultSubobject<>로 UInventoryComponent를 생성하세요. 각 패턴의 사용 시점을 확인하세요.
DefaultEngine.ini에서 gc.TimeBetweenPurgingPendingKillObjects, gc.AllowIncrementalGC 설정을 조정하고, ForceGarbageCollection()과 stat gc 명령으로 GC 성능을 프로파일링하세요. 대량 몬스터 스폰/파괴 시나리오에서 GC 히칭을 측정하세요.