DevDino

[Unity 기술면접] C# Garbage Collector 본문

Unity/기술면접

[Unity 기술면접] C# Garbage Collector

데브다이노 2026. 1. 1. 16:03

"C#의 Garbage Collection은 어떻게 동작하나요?"

가비지 컬렉션(Garbage Collection) 동작 방식

C#은 개발자가 직접 메모리를 해제(free, delete)할 필요 없이, CLR(공용 언어 런타임) 환경의 가비지 컬렉터(GC, Garbage Collector)가 메모리 관리를 자동으로 수행합니다. 편리함의 대가로 성능 비용이 따르기 때문에, Unity 개발자라면 GC의 내부 동작 방식을 이해하고 최적화하는 것이 필수적입니다.

 

GC의 개념

GC의 핵심은 "루트(Root)에서 도달할 수 없는 객체는 쓰레기(Garbage)로 간주하고 메모리에서 해제한다"는 것입니다.
하지만 힙(Heap) 영역 전체를 매번 검사하는 것은 너무 무겁기 때문에, C#의 GC는 "최근에 생성된 객체일수록 빨리 가비지가 될 확률이 높다"는 가설을 바탕으로 메모리를 세대(Generation)로 나누어 관리합니다.

 

세대 기반 메모리 관리(Generation)

GC는 힙 메모리를 세 가지 세대로 구분하여 검사 횟수와 비용을 최적화합니다.

 

- Gen 0 (0세대): 방금 새로 생성된 객체들이 할당되는 영역입니다. (수명이 매우 짧은 임시 객체들)
- Gen 1 (1세대): Gen 0에서 진행된 GC에서 살아남은 객체들이 승격(Promotion)되어 이동하는 중간 단계 영역입니다.
- Gen 2 (2세대): Gen 1에서도 살아남은, 수명이 매우 긴 객체들(싱글턴, 정적 데이터 등)이 머무는 영역입니다.

 

대부분의 객체는 금방 참조가 끊기므로, GC는 주로 0세대(Gen 0) 영역만 빠르게 검사하여 시스템의 과부하를 줄입니다.

 

GC의 3단계 동작 메커니즘(Mark -> Sweep -> Compact)

GC가 트리거 되면(Gen 0 공간 부족 등), 다음 3단계를 거쳐 메모리를 정리합니다.

 

- Mark(표시): Root에서부터 연결된 모든 참조를 추적하여, 현재 사용 중(도달 가능)인 객체에 마킹을 합니다.

- Sweep(쓸어내기): 힙 영역을 순회하며 마킹되지 않은 객체(도달 불가능한 가비지)를 메모리에서 해제합니다.

- Compact(압축): 살아남은 객체들을 메모리의 한쪽으로 밀어서 연속되게 배치합니다. 이 과정을 통해 메모리 파편화(Fragmentation)를 방지하고, 이후 새로운 객체를 더 빠르게 할당할 수 있게 합니다. (이때 객체의 메모리 주소가 바뀌므로 참조 포인터도 자동으로 업데이트됩니다.)

 

GC의 장단점

장점
메모리 누수나 이중 해제 같은 치명적인 버그를 예방하며, 개발자의 생산성을 크게 높여줍니다.


단점
GC가 실행될 때 메모리를 안전하게 조작하기 위해 시스템을 일시적으로 멈추는 Stop-The-World(STW) 현상이 발생합니다. 이는 실시간 렌더링이 중요한 게임에서 치명적인 프레임 드랍(렉)을 유발합니다.

 

Unity에서의 GC 최적화 필수 기법

Unity에서는 이 Stop-The-World 현상을 막기 위해 쓸데없는 가비지(특히 Gen 0) 생성을 최소화하는 것이 가장 중요합니다.


- 오브젝트 풀링(Object Pooling): 총알, 이펙트, 몬스터처럼 빈번하게 생성되고 파괴되는 객체나, 스크롤 뷰에서 갱신되는 UI 항목들을 매번 new로 할당하지 않고 미리 생성해 둔 뒤 재사용하여 GC의 개입을 막아야 합니다.


- 문자열 최적화(StringBuilder 사용): C#에서 string은 불변(Immutable) 객체입니다. 텍스트를 실시간으로 조합할 때 string + string 연산을 사용하면 매번 새로운 임시 문자열 객체가 힙에 할당되므로, 잦은 문자열 변경에는 StringBuilder를 사용해야 합니다.


- Update 루프 내 할당 금지: 초당 수십 번 호출되는 Update()문 안에서는 새로운 객체 할당(new), 무명 메서드(람다) 캡처로 인한 클로저 생성, 그리고 값 타입을 참조 타입으로 변환하는 박싱(Boxing) 연산을 철저히 피하도록 설계해야 합니다.

요약
C#의 GC는 편안한 개발 환경을 제공하지만, 방심하면 잦은 Stop-The-World로 게임의 퀄리티를 떨어뜨립니다. 따라서 구조적인 세대 기반 동작을 이해하고, 의미 없는 임시 객체 생성을 통제하는 것이 핵심입니다.