[UE5] 이득우 Part 1. 12강 언리얼 엔진의 메모리 관리
C++ 언어 메모리 관리 문제점
- 메모리 주소에 직접 접근하는 포인터를 사용해 관리
- 직접 new, delete를 짝 맞춰줘야 함
- 잘못된 포인터 예시
- 메모리 누수(Leak): new를 했는데 delete를 하지 않아 힙에 메모리가 남아있음
- Dangling 포인터: 이미 할당 해제한 오브젝트의 주소를 가리키는 포인터
- Wild 포인터: 값이 초기화되지 않아 이상한 주소를 가리키는 포인터
- 잘못된 포인터 값은 다양한 문제가 생기며 프로그램을 종료시킬 수 있음
- 규모가 커지고 복잡해질수록 실수할 확률이 증가
- C++ 이후 언어(JAVA/C# 등)는 문제 해결을 위해 포인터 대신 가비지 컬렉션 시스템을 도입
가비지 컬렉션 시스템
- 프로그램에서 더 이상 사용하지 않는 오브젝트를 자동으로 감지해 메모리를 회수하는 시스템
- 동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소를 사용해 사용되지 않는 메모리를 추적
- 마크-스윕(Mark-Sweep) 방식의 가비지 컬렉션
- 저장소에서 최초 검색을 시작하는 루트 오브젝트를 표기
- 루트 오브젝트가 참조하는 객체를 찾아 Mark
- 마크된 객체로부터 다시 참조하는 객체를 찾아 Mark하고 반복
- 저장소에 마크된 객체와 마크되지 않은 객체의 두 그룹이 생김
- 카비지 컬렉터가 저장소에서 마크되지 않은 객체들을 메모리 회수(Sweep)
언리얼 엔진의 가비지 컬렉션 시스템
- 마크-스윕 방식의 가비지 컬렉션 시스템을 자체적으로 구축
- 지정된 주기마다 몰아서 제거 (CGCycle, 기본 60초)
- 성능 향상을 위해 병렬 처리, 클러스터링과 같은 기능을 탑재
가비지 컬렉션을 위한 객체 저장소
- 관리되는 모든 UObject의 정보를 저장하는 전역 변수: GUObjectArray
- GUObjectArray의 각 요소에는 Flag가 설정되어 있음
- 가비지 컬렉터가 참고하는 주요 플래그
- Grabage 플래그: 다른 언리얼 오브젝트로부터의 참조가 없어 회수 예정인 오브젝트
- RootSet 플래그: 다른 언리얼 오브젝트로부터 참조가 없어도 회수하지 않는 특별한 오브젝트
가비지 컬렉터의 메모리 회수
- 가비지 컬렉터는 지정된 시간에 따라 주기적으로 회수
- Garbage 플래그로 설정된 오브젝트를 파악하고 메모리를 안전하게 회수
- Garbage 플래그는 시스템이 알아서 설정
- 한번 생성된 오브젝트를 삭제하기 위해서는 C++의 딜리트 키워드를 사용해서 바로 삭제하는 것이 아니라 레퍼런스 정보를 없애 언리얼의 가비지 컬렉터가 자동으로 메모리를 회수하도록 설정
RootSet 플래그의 설정
- 중요해서 시스템이 실행되는 동안 살아있어야 함
- AddToRoot 함수를 호출해 RootSet 플래그를 설정
- 메모리 회수로부터 보호
- RemoveFromRoot 함수를 호출해 RootSet 플래그를 제거
UObject를 통한 포인터 문제 해결
- 메모리 누수 문제
- 언리얼 오브젝트는 가비지 컬렉터를 통해 자동으로 해결
- C++ 오브젝트는 직접 신경써야 함 (스마트 포인터 활용)
- Dangling 포인터 문제
- UObject는 이를 탐지하기 위한 함수 제공 ::IsValid()
- C++ 오브젝트는 직접 신경써야 함 (스마트 포인터 활용)
- Wild 포인터 문제
- UObject는 UPROPERTY 속성을 지정하면 자동으로 nullptr 초기화
- C++ 오브젝트의 포인터는 직접 nullptr로 초기화(또는 스마트 포인터)
회수되지 않는 언리얼 오브젝트
- 언리얼 엔진 방식으로 참조를 설정한 UObject
- UPROPERTY로 참조된 UObject
- AddReferencedObject 함수를 통해 참조를 설정
- RootSet으로 지정된 UObject
- 오브젝트 선언 기본 원칙
- 오브젝트 포인터는 가급적 UPROPERTY로 선언
- 메모리는 가비지컬렉터가 자동으로 관리하도록 위임
일반 클래스에서 언리얼 오브젝트를 관리하는 경우
- UPROPERT를 사용하지 못하는 일반 C++ 클래스가 언리얼 오브젝트를 관리해야 하는 경우
- FGCObject 클래스를 상속받은 후 AddReferencedObjects 함수를 구현
- 함수 구현부에서 관리할 UObject를 추가
언리얼 오브젝트의 관리 원칙
- 생성된 UObject를 유지하기 위해 레퍼런스 참조 방법을 설계할 것
- 언리얼 오브젝트 내의 언리얼 오브젝트: UPROPERTY 사용
- 일반 C++ 오브젝트 내의 언리얼 오브젝트: FGCObject의 상속 후 구현
- 생성된 UObejct를 강제로 지우려 하지 말 것
- 참조를 끊는다는 생각으로 설계
- 가비지 컬렉터에게 회수를 재촉할 수는 있음
- ForceGarbageCollection 함수
- 콘텐츠 제작에서 Destroy함수를 사용할 수 있으나 내부 동작은 동일
실습
- CGCycle 3초 단축 설정
- GameInstance에서 Init, Shutdown 오버라이드
- Init에서 오브젝트 생성 -> 3초 대기 -> 플레이 중지 -> Shutdown에서 오브젝트 유효성 확인