동적으로 할당한 메모리(힙영역 메모리) 중 더이상 쓰이지 않는 메모리를 자동으로 해제해주는 시스템입니다.
NullPointer참조
문제DoubleFree
문제메모리를 할당받은 객체들이 레퍼런스 카운트라는 숫자를 통해서 할당된 횟수를 가지고 있는 방식입니다.
레퍼런스 카운트가 0이 되면 알아서 할당해제 해주는 방식입니다.
서로가 서로를 할당하는 순환참조 문제
가 발생해 메모리 누수가 발생할 수 있다.
루트에서 그래프 순회를 통해 해당 객체에 접근가능한지의 여부를 파악하고(Mark)
접근가능하지 않은 객체를 할당해제 해주는(Sweep) 방식입니다.
루트(GC Root)에 연결된 객체들을 Reachable
, 연결되지 않은 객체들을 Unreachable
이라고 부른다.
추가적으로 메모리 파편화를 막기위해 Compaction
을 진행하기도 한다.
메모리가 할당된 힙 영역을 Young Generation
과 Old Generation
으로 나누고 YoundGeneration
은 또다시 Eden, Survivor0, Survivor1로 나눕니다.
언리얼엔진에서는 GC를 제공하지 않는 C/C++
언어로 제작을 했기때문에 자체적인 GC시스템을 만들었습니다.
언리얼의 GC는 표준 GC모델을 사용하지만 멀티 스레드 기반 가비지 컬렉션(Multi-Threaded Garbage Collection)알고리즘을 지원해줍니다. 이때는 여러 개의 쓰레드를 사용하여 병렬적으로 GC를 수행합니다.
언리얼에서는 RootSet
객체를 지정해두고 RootSet
부터 GC를 수행하게 됩니다.
이때 RootSet
이란 영구적으로 활성상태인 객체를 뜻합니다.
RootSet
에서 시작하여 리플렉션을 통해 RTTI
를 동적으로 검사하여 객체간의 참조관계를 조사언리얼 엔진소스 GarbageCollection.cpp
의 CollectGarbageInternal(...)
에서 수행되고 아래와 같은 순서를 따릅니다.
Mark unreachable
→ Mark reachable
→ Sweep
→ Shrink Hash Table(Compaction)
위 과정은 모두 CollectGarbageInternal(...)
에서 실행되고 개념설명이 끝난 뒤, 모든 실행과정을 보여드리도록 하겠습니다.
PerformReachabilityAnalysis(..) → FRealtimeGC::MarkObjectsAsUnreachable에서 수행되며 Unreachable한 경우 UnreachableFlag
를 부여합니다.
PerformReachabilityAnalysis(..) → FRealtimeGC::PerformReachabilityAnalysisOnObjectsInternal에서 수행되며 UObject
갯수가 많을 수록 많은 시간이 소요됩니다.
대략 10만개 이상의 오브젝트가 존재할 경우 GC clusters
를 사용하는 방법을 통해 시간을 줄일 수 있습니다.
또한 이 단계에서는 이득우 강사님의 12강 언리얼 메모리 강의에서 봤던 FGCObject
의 AddReferencedObjects
함수를 통해 강제적으로 GC에 수집되지 않도록 해주는 과정이 포함됩니다.
Unreachable한 객체를 파괴/제거하는 단계로 IncrementalPurgeGarbage()에서 수행됩니다. IncrementalPurgeGarbage()내부에서는 UnhashUnreachableObjects()함수를 통해 UObject
의 unhash하는 작업도 추가적으로 진행합니다.
모든 가비지가 제거된 후에 실행되는 해시 압축(Compact)단계로 ShrinkUObjectHashTables() → FUObjectHashTables::ShrinkMaps()에서 수행됩니다.
여기서 해시테이블이란 모든 UObject객체
를 반복적으로 검사하지 않고 유형에 따라 개체를 빠르게 가져올 수 있는 편리한 테이블입니다.
언리얼에서는 GC와 관련된 로그를 볼 수 있는 기능을 제공해줍니다.
이 디버깅 기능으로 GC의 총 실행시간과 각 단계별로 걸린 시간 등 GC디버깅 정보를 확인할 수 있습니다.
로그를 보기 위해서 먼저 콘솔에 log loggarbage verbose
명령어를 입력해주면 됩니다.
그리고 GC주기를 본인이 원하는 시간으로 정하면 아래 그림처럼 로그를 확인할 수 있습니다.
그리고 콘솔 창에서 obj gc명령어를 통해서 강제로 GC를 발생시킬 수도 있습니다.
강제로 발생시키지 않으면 설정해둔 GC시간마다 발생하게 됩니다.
언리얼의 GC에 대한 동작방식은 실제로 직접 디버깅하면서 확인하면 더 명확하게 와닿을 수 있습니다.
소스코드에 Breakpoint를 찍고 GC의 흐름이 어떻게 동작하는지 직접 경험해보면서 배우셨으면 좋겠습니다.
JVM (https://steady-coding.tistory.com/584)
.NET(https://prodotnetmemory.com/slides/UnderstadingGC/#14)
GC(https://mikelis.net/memory-management-garbage-collection-in-unreal-engine/)
엘리의 GC : https://youtu.be/Fe3TVCEJhzo
조엘의 GC : https://youtu.be/FMUpVA0Vvjw
언리얼 GC(https://forums.unrealengine.com/t/garbage-collector-internals/501800, https://forums.unrealengine.com/t/primer-debugging-garbage-collection-performance/661734#measuring-garbage-collection-times-2)