Table of Contents
언리얼 엔진의 자동 메모리 관리
C++ 언어 메모리 관리의 문제점
- C++은 저수준으로 메모리 주소에 직접 접근하는 포인터를 사용해 오브젝트를 관리한다.
- 그러다보니 프로그래머가 직접 할당(new)과 해지(delete) 짝 맞추기를 해야 한다.
- 이를 잘 지키지 못하는 경우 다양한 문제가 발생할 수 있음
- 잘못된 포인터 사용 예시
- 메모리 누수(Leak)
- ⇒ new를 했는데 delete 짝을 맞추지 못함 → 힙에 메모리가 그대로 남아있음
- 허상(Dangling)포인터
- ⇒ (다른곳에서) 이미 해제해 무효화된 오브젝트의 주소를 가리키는 포인터
- 와일드(wild)포인터
- ⇒ 값이 초기화되지 않아 엉뚱한 주소를 가리키는 포인터
- 메모리 누수(Leak)
- 잘못된 포인터 값은 다양한 문제를 일으키며, 한 번의 실수는 프로그램을 종료시킴
- 게임 규모가 커지고 구조가 복잡해질수록 프로그래머가 실수할 확률은 크게 증가한다.
[!note]
C++ 이후에 나온 언어 Java/C# 은 이런 고질적인 문제를 해결하기 위해
포인터를 버리고 대신 가비지 컬레션 시스템을 도입함
가비지 컬렉션 시스템
- 프로그램에서 더 이상 사용하지 않는 오브젝트를 자동으로 감지해 메모리를 회수하는 시스템.
- 동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소를 사용해 사용되지 않는 메모리를 추적
- 마크-스윕(Mark-Sweep) 방식의 가비지 컬렉션
- 저장소에서 최초 검색을 시작하는 루트 오브젝트를 표기한다.
- 루트 오브젝트가 참조하는 객체를 찾아 마크(Mark)한다.
- 마크된 객체로부터 다시 참조하는 객체를 찾아 마크하고 이를 계속 반복한다.
- 이제 저장소에는 마크된 객체와 마크되지 않은 객체의 두 그룹으로 나뉜다.
- 가비지 컬렉터가 저장소에서 마크되지 않은 객체(가비지)들의 메모리를 회수한다.(Sweep)
언리얼 엔진의 가비지 컬렉션 시스템
- 마크-스윕 방식의 가비지 컬렉션 시스템을 자체적으로 구축함
- 지정된 주기마다 몰아서 없애도록 설정되어 있음 (GCCycle → 기본 값 60초)
- 성능 향상을 위해 병렬 처리, 클러스터링과 같은 기능을 탑재함
가비지 컬렉션을 위한 객체 저장소
- 관리되는 모든 언리얼 오브젝트의 정보를 저장하는 전역 변수 ⇒
GUObjectArray
GUObjectArray
의 각 요소에는 플래그(Flag)가 설정되어 있음- 가비지 컬렉터가 참고하는 주요 플래그
Garbage
플래그 : 다른 언리얼 오브젝트로부터의 참조가 없어 회수 예정인 오브젝트RootSet
플래그 : 다른 언리얼 오브젝트로부터 참조가 없어도 회수하지 않는 특별한 오브젝트
가비지 컬렉터의 메모리 회수
- 가비지 컬렉터는 지정된 시간에 따라 주기적으로 메모리를 회수한다. (기본 값 60초)
- Garbage 플래그로 설정된 오브젝트를 파악하고 메모리를 안전하게 회수함.
- Garbage 플래그는 수동으로 설정하는 것이 아닌, 시스템이 알아서 설정함
루트셋 플래그의 설정
- AddToRoot 함수를 호출해 루트셋 플래그를 설정하면 최초 탐색 목록으로 설정됨.
- 루트셋으로 설정된 언리얼 오브젝트는 메모리 회수로부터 보호받음
RemoveFromRoot
함수를 호출해 루트셋 플래그를 제거할 수 있음
but
콘텐츠 관련 오브젝트에 루트셋을 설정하는 방법은 권장되진 않음
언리올 오브젝트를 통한 포인터 문제의 해결
- 메모리 누수 문제
- 언리얼 오브젝트는 가비지 컬렉터를 통해 자동으로 해결
- C++ 오브젝트는 직접 신경써야함 (스마트 포인터 라이브러리의 활용)
- 댕글링 포인터 문제
- 언리얼 오브젝트는 이를 탐지하기 위한 함수를 제공함
::IsValid()
- C++ 오브젝트는 직접 신경써야 함. (스마트 포인터 라이브러리의 활용)
- 언리얼 오브젝트는 이를 탐지하기 위한 함수를 제공함
- 와일드 포인터 문제
- 언리얼 오브젝트에
UPROPERTY
속성을 지정하면 자동으로nullptr
로 초기화 해 줌 - C++ 오브젝트의 포인터는 직접
nullptr
로 초기화 할 것 (또는 스마트 포인터 라이브러리를 활용)
- 언리얼 오브젝트에
회수되지 않는 언리얼 오브젝트
- 언리얼 엔진 방식으로 참조를 설정한 언리얼 오브젝트
UPROPERTY
로 참조된 언리얼 오브젝트 (대부분의 경우 이를 사용)AddReferencedObject
함수를 통해 참조를 설정한 언리얼 오브젝트
- 루트셋(RootSet)으로 지정된 언리얼 오브젝트
title: 오브젝트 선언의 기본 원칙
오브젝트 포인터는 가급적 UPROPERTY로 선언하고,
메모리는 가비지컬렉터가 자동으로 관리하도록 위임한다.
일반 클래스에서 언리얼 오브젝트를 관리하는 경우
UPROPERTY
를 사용하지 못하는 일반 C++클래스가 언리얼 오브젝트를 관리해야 하는 경우- FGCObject 클래스를 상속받은 후
AddReferencedObjects
함수를 구현한다. - 함수 구현 부에서 관리할 언리얼 오브젝트를 추가해 줌
언리얼 오브젝트의 관리 원칙
- 생성된 언리얼 오브젝트를 유지하기 위해 레퍼런스 참조 방법을 설계할 것
- 언리얼 오브젝트 내의 언리얼 오브젝트 :
UPROPERTY
사용 - 일반 C++오브젝트 내의 언리얼 오브젝트 :
FGCObject
의 상속 후 구현
- 언리얼 오브젝트 내의 언리얼 오브젝트 :
- 생성된 언리얼 오브젝트는 강제로 지우려 하지 말 것
- 참조를 끊는다는 생각으로 설계할 것
- 가비지 컬렉터에게 회수를 재촉할 수는 있음(
ForceGarbageCollction
함수) - 콘텐으 제작에서
Destory
함수를 사용할 수 있으나, 결국 내부 동작은 동일함(가비지 컬렉터에 위임)
가비지 컬렉션 테스트 환경 제작
- 프로젝트 설정에서 가비지 컬렉션 GCCycle 시간을 3초로 단축 설정
- 새로운
GameInstance
의 두 함수를 오버라이드Init
: 어플리케이션이 초기화될 때 호출Shutdown
: 어플리케이션이 종료될 때 호출
- 테스트 시나리오
- 플레이 버튼을 누를 때
Init
함수에서 오브젝트를 생성하고 - 3초 이상 대기해 가비지 컬렉션을 발동
- 플레이 중지를 눌러
Shutdown
함수에서 생성한 오브젝트의 유효성을 확인
- 플레이 버튼을 누를 때
이 글은 옵시디언에서 마크다운으로 작성되었습니다.
블로그에서는 지원하지 않는 포맷이있어, 보기 불편할 수 있습니다.
'UE5 > 이득우의 언리얼 프로그래밍 공부 노트' 카테고리의 다른 글
[Part1] 14.언리얼 오브젝트 관리 2 - 패키지 (1) | 2024.03.13 |
---|---|
[Part1] 13.언리얼 오브젝트 관리 1 - 직렬화 (1) | 2024.03.12 |
[Part1] 11.언리얼 컨테이너 라이브러리(2)-구조체와 Map (1) | 2023.12.31 |
[Part1] 10.언리얼 컨테이너 라이브러리(1) - Array와 Set (0) | 2023.12.24 |
[Part1] 9.언리얼C++ 설계(3) - 델리게이트 (1) | 2023.12.17 |