UObject Lifecycle과 GC 최적화

Unreal Engine의 UObject는 엔진이 제공하는 기본적인 객체 시스템이다.
일반 C++ 객체와는 달리, Garbage Collector(GC)에 의해 메모리 해제가 자동으로 관리된다.
하지만 GC는 비용이 큰 작업이며, 구조를 잘 이해하지 못하면 메모리 누수나 불필요한 객체 유지로 성능 저하가 발생할 수 있다.

기본 개념: UObject의 수명 주기

  1. NewObject<T>() 또는 DuplicateObject() 등으로 객체 생성
  2. 참조가 살아있는 동안 GC에서 유지됨
  3. 더 이상 유효한 참조가 없고, 다음 GC 주기에 포함되면 삭제 대상이 됨
  4. GC는 MarkPendingKill() 플래그를 통해 삭제 예정 객체를 표시
  5. 이후 실제 Destroy() 호출 또는 메모리 회수로 제거됨

핵심 요소 정리

요소설명
IsValid(Object)nullptr이 아니고 MarkPendingKill() 상태도 아닌지 확인하는 함수
MarkPendingKill()삭제 예정 표시. GC 대상이 됨
RF_Transient저장되지 않고, Editor/런타임 재시작 시 사라지도록 만드는 플래그
AddToRoot()GC 대상에서 제외됨. 반드시 RemoveFromRoot()와 함께 사용해야 함
TWeakObjectPtrGC된 객체는 자동으로 nullptr가 됨. 안전한 참조 방식
TStrongObjectPtrGC 보호가 되는 스마트 포인터. AddToRoot 없이도 안전함

MarkPendingKill()과 IsValid()

GC가 객체를 제거할 때는 내부적으로 MarkPendingKill()을 호출하여 객체가 삭제 대상임을 표시한다.
이 상태인 객체에 접근하면 예기치 않은 동작이나 크래시가 발생할 수 있으므로, 항상 IsValid()로 확인해야 한다.

  if (IsValid(MyObject))
{
    MyObject->DoSomething();
}
  

IsValid()는 다음 조건을 모두 확인한다:

  • MyObject != nullptr
  • !MyObject->IsPendingKill()

RF_Transient 플래그

RF_Transient는 객체가 저장되지 않도록 만드는 중요한 플래그이다.

  MyObject->SetFlags(RF_Transient);
  

이 플래그가 설정된 객체는 다음과 같은 특징이 있다:

  • 저장되지 않음 (SaveGame, Editor Save 등 무시)
  • 객체가 일시적인 경우 적절한 선택
  • 보통 UI 위젯, 비주얼 이펙트, 미리보기 객체 등에 사용

AddToRoot() / RemoveFromRoot()

Garbage Collector는 UObject를 Root Set에 포함하지 않으면 언제든지 삭제할 수 있다.
따라서 수동으로 GC 대상에서 제외하고 싶다면 AddToRoot()를 호출해야 한다.

  MyObject->AddToRoot(); // 절대 GC 안 됨
...
MyObject->RemoveFromRoot(); // 다시 GC 가능해짐
  

주의: AddToRoot()를 호출하면 반드시 RemoveFromRoot()를 해줘야 하며, 그렇지 않으면 메모리 누수가 발생할 수 있다.

스마트 포인터를 활용한 GC 안전 처리

TWeakObjectPtr

  TWeakObjectPtr<UMyObject> WeakRef = MyObject;
...
if (WeakRef.IsValid())
{
    WeakRef->DoSomething();
}
  
  • GC가 객체를 제거하면 자동으로 nullptr이 된다.
  • 유효성 검사 후 사용해야 한다.

TStrongObjectPtr

  TStrongObjectPtr<UMyObject> StrongRef = TStrongObjectPtr<UMyObject>(MyObject);
  
  • 객체가 GC에 의해 제거되지 않도록 보호함
  • AddToRoot 없이도 안전함
  • 라이프사이클을 명확하게 관리하고 싶을 때 유용하다

일반적인 최적화 전략

  1. 의미 없는 UObject Tick 제거
    UObject는 기본적으로 Tick 대상이 아니지만, 타이머나 델리게이트 등에 연결되면 Tick처럼 동작할 수 있다.
    필요 없으면 해제해야 한다.

  2. Transient 플래그 적극 활용
    저장 필요 없는 일시적 객체는 모두 RF_Transient로 처리하는 것이 좋다.

  3. AddToRoot 남용 금지
    GC를 완전히 막기 때문에 필요한 경우에만 사용해야 한다.

  4. 스마트 포인터 사용
    TWeakObjectPtr, TStrongObjectPtr를 통해 명시적이고 안전한 객체 관리가 가능하다.

  5. UObject를 new/delete로 만들지 말 것
    반드시 NewObject<T>(), DuplicateObject() 또는 CreateDefaultSubobject() 사용