본문 바로가기
Unreal Engine

언리얼엔진은 메모리관리를 어떤방식으로 하나

by PainDiver 2022. 11. 27.

 

 

면접에서 나온 질문이다.

 

하.. 모르겠다.. 뭐라고 대답을 해야할지 몰라서 대답 못했다.

 

안잊으려고 여기에 쓴다.

 

찾아보니까 언리얼엔진은 메모리를 이렇게 관리한다고 한다

 

' 언리얼은 스마트 포인터와 가비지 컬렉터(GC)를 모두 사용하여 메모리를 관리한다.

일반적으로 네이티브한 부분에서는 스마트 포인터를 사용하고,

UObject와 관련된 부분에서는 GC를 사용한다.'

 

이거였다.. 가비지 콜렉터의 존재를 잊고있었다.

 

IOCP서버를 언리얼엔진에 연동해 볼 때는, 언리얼엔진의 UObject가 아니라면 T스마트 포인터로 관리하는게 좋다고는 알고 있었다. 그리고 U클래스는 딱히 스마트포인터로 안 만들었는데, 뭔가 언리얼엔진 만의 메모리 관리방법이 있다고 생각했기 때문이다.

 

그리고 그게 가비지콜렉터였다.

 

가비지 콜렉터에는 세가지 규칙이 있다고한다.

 

 

1. UPROPERTY 선언은 클래스 내부 멤버 변수가 클래스의 객체의 수명과 운명을 함께할 경우 한다.

 

2. 멤버가 가리키는 포인터가 엔진이 인식하거나 관리하지 않는 메모리 영역을 가리키도록 만들면 안됨

 

3. TArray 는 UObject 또는 자식들에 대한 포인터를 안전하게 담을 수 있는 유일한 컨테이너

 

그렇다면 이 세가지 규칙이 왜 생겨났을까?

 

 

'가비지 콜렉터는 UObject들을 스마트포인터마냥 RefCount를 세서 삭제시키는데, 이는 엔진의 Reference Graph에 의해 판단된다. 이 그래프 루트에는 "Root Set" 이라 지정된 오브젝트 세트가 존재하며, "Root Set"에 포함된 녀석들은 Garbage Collection 대상에서 제외된다. 어떤 언리얼 오브젝트도 UObjectBaseUtility::AddToRoot 함수를 통해 "Root Set"에 추가시킬 수 있다.'

 

'Actor의 경우 SpawnActor를 통해 생성될때 AddReferencedObject를 통해 레퍼런스 리스트에 추가되며 GC를 피할 수 있다. Component는 Actor에 붙어있기 때문에 무사하다.'

 

또한 TArray에 담겨있는 요소들은 레퍼런스 카운팅이 되므로 GC의 대상이 되지않는다.

 

즉, 가비지 콜렉터에 의해서 메모리할당이 해제되는 것을 피하기 위해선 AddToRoot 함수를 이용해서 RootSet에 추가시키거나, AddReferencedObject를 통해 레퍼런스 리스트에 추가해야 하고, SpawnActor를 이용한 액터클래스는 GC에 의해서 파괴될 걱정은 하지 않아도 된다.

 

그렇다면 NewObject로 U 오브젝트를 만드는경우는? UObject이긴 하지만, AddRefrencedObject는 따로 실행되어야하거나 UPROJECT 지정자를 통해서 "저 참조되고 있습니다" 라는 명시가 필요하다. 그렇게 GC를 피해야한다.

 

결론적으로 모든 UObject들은 GC의 관리대상이고, 이 중 참조가 되지 않은 개체들은 주기적으로 GC에 의해 파괴되기 때문에, 자신을 참조해줄 무언가가 없으면 언제 제거될지 모른다.

 

그래서, 오브젝트의 가비지 콜렉터의 쓰레기청소를 피하기 위한 네가지 방법은

 

1. UPROPERTY 선언 (강참조)

 

2. AddReferencedObject 함수

 

3. RootSet에 오브젝트 추가 (일반적으로 불필요하다 한다)

 

4. TArray에 담기 

 

 

반대로 가비지콜렉션을 요청하는 방법으로는 Destroy 함수를 사용하는 것이다.

엔진 시스템에 의해서 가비지콜렉션이 발생하므로, 가비지 콜렉션을 즉시 실행시키는 것은 아니다.

 

허나, 가비지 콜렉션을 강제로 실행시킬 수도 있다고 한다.

World::ForceGarbageCollection(bool bFullPurge) 함수를 통해 강제 GC를 수행할 수 있다.

 

 

또, 가비지 콜렉션 이후, 로우포인터로 해당 가비지 콜렉션당한 포인터 객체를 접근하지 않는게 좋다.

로우포인터의 경우 널포인터 체크로 객체의 생존을 체크해야하는데,

가비지콜렉션의 결과는 널포인터 체크로 알 수 없고,

가비지 콜렉션을 막지 않아도 되면 TWeakObjectPtr과 같은 약참조포인터를 사용해서, 사용 시에 생존체크만 해주자.

 

 

 

[Reference]

 

[Unreal] 언리얼 메모리 관리 시스템 (Smart Pointer, GC) (tistory.com)

수까락의 프로그래밍 이야기 (zum.com)