콘텐츠로 이동

UE5 메모리 & GC

Unreal Engine의 메모리 관리는 두 축으로 나뉩니다. UObject 기반 가비지 컬렉션(GC)C++ 스마트 포인터 입니다. 두 시스템의 경계를 이해하지 못하면 메모리 누수 또는 댕글링 포인터가 발생합니다.


UObject를 상속한 객체는 UE의 GC가 수명을 관리합니다. GC는 마크 앤 스윕(Mark & Sweep) 방식으로 루트 셋에서 도달 불가능한 UObject를 수집합니다.

GC 루트 셋
├─ AddToRoot()로 등록된 객체
├─ UPROPERTY로 참조된 객체
└─ 현재 로드된 UWorld / Level

기본적으로 매 60초마다 또는 힙 임계값 초과 시 실행됩니다.

; DefaultEngine.ini
[/Script/Engine.GarbageCollectionSettings]
gc.TimeBetweenPurgingPendingKillObjects=60.0
gc.MaxObjectsNotConsideredByGC=1

UObject 포인터는 반드시 UPROPERTY로 선언해야 GC가 참조를 추적합니다.

UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// ✅ GC가 참조를 추적 — 이 객체가 살아있는 동안 Component도 수집 안 됨
UPROPERTY()
UStaticMeshComponent* MeshComp;
// ❌ UPROPERTY 없음 — GC가 참조를 모르므로 수집될 수 있음
UStaticMeshComponent* UntrackedComp;
};

포인터대상특징
TSharedPtr<T>비UObject C++ 객체참조 카운팅, 스레드 안전 옵션
TWeakPtr<T>비UObject C++ 객체소유권 없음, 유효성 확인 필요
TUniquePtr<T>비UObject C++ 객체단독 소유, 복사 불가
TWeakObjectPtr<T>UObjectUObject GC와 연동, 수집 시 자동 null
TObjectPtr<T>UObject (UE 5.0+)UPROPERTY 대체, 에디터 추적 강화

UObject를 소유하지 않고 참조만 유지할 때 사용합니다. GC가 대상을 수집하면 자동으로 무효화됩니다.

// 약한 참조 저장
TWeakObjectPtr<AEnemy> WeakEnemy;
void AMyController::TrackEnemy(AEnemy* Enemy)
{
WeakEnemy = Enemy; // 소유권 없이 참조만 저장
}
void AMyController::Tick(float DeltaTime)
{
// IsValid()로 유효성 확인 후 사용
if (WeakEnemy.IsValid())
{
WeakEnemy->TakeDamage(10.f, ...);
}
}

// ❌ AddToRoot 후 RemoveFromRoot 누락 → GC 영구 제외
UMyObject* Obj = NewObject<UMyObject>();
Obj->AddToRoot();
// RemoveFromRoot() 호출 안 함
// ✅ 사용 완료 후 반드시 해제
Obj->RemoveFromRoot();
// ❌ 람다가 UObject를 강하게 캡처 → GC 수집 불가
TFunction<void()> Callback = [this]()
{
// this(AActor*)가 수집되어도 람다가 참조를 유지
DoSomething();
};
// ✅ TWeakObjectPtr로 캡처
TWeakObjectPtr<ThisClass> WeakThis(this);
TFunction<void()> Callback = [WeakThis]()
{
if (WeakThis.IsValid())
WeakThis->DoSomething();
};
// ❌ A → B → A 순환 참조 → 둘 다 수집 안 됨
UPROPERTY() AActorB* RefToB; // A가 B를 참조
UPROPERTY() AActorA* RefToA; // B가 A를 참조
// ✅ 한 쪽을 TWeakObjectPtr로 변경
UPROPERTY() TWeakObjectPtr<AActorA> WeakRefToA;

// IsValid() — GC 대기(PendingKill) 포함 null 체크 (일반적으로 이것 사용)
if (IsValid(MyActor)) { }
// IsValidLowLevel() — 포인터 자체의 메모리 유효성만 체크 (엔진 내부용)
// 일반 게임플레이 코드에서는 사용 금지

Unreal Insights → Memory Insights 탭
└─ 할당 추적, 태그별 메모리 사용량 확인
콘솔 명령어:
memreport -full 전체 메모리 리포트 파일 생성
obj list class=Texture 클래스별 UObject 목록 출력
stat memory 실시간 메모리 통계

; DefaultEngine.ini
[/Script/Engine.GarbageCollectionSettings]
; 수집 주기 (초) — 액션 게임은 30~60s 권장
gc.TimeBetweenPurgingPendingKillObjects=60.0
; 증분 GC로 스파이크 분산 (UE 5.0+)
gc.AllowIncrementalReachabilityAnalysis=1
gc.IncrementalGCTimePerFrame=2.0
// 보스 등장 직전 GC를 미리 강제 실행해 전투 중 히치 방지
void AMyGameMode::OnBossEncounterBegin()
{
// true: 동기 수집 — 다음 프레임 전에 완료
GEngine->ForceGarbageCollection(true);
UE_LOG(LogTemp, Log, TEXT("Pre-boss GC complete"));
}
// UE4 스타일 (여전히 동작하나 UE5에서는 권장하지 않음)
UPROPERTY()
UStaticMeshComponent* MeshComp;
// UE5 표준 — TObjectPtr는 에디터 빌드에서 잘못된 접근을 즉시 감지
UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComp;

일괄 마이그레이션은 엔진 자동화 도구를 사용합니다.

Tools → Convert Project to TObjectPtr (UE 5.0+ 제공)