Skip to content

Unity 메모리 & GC 최적화

Unity 게임에서 GC 스파이크는 프레임 드롭의 주요 원인 중 하나입니다. Mono와 IL2CPP 모두 Boehm GC(비세대적, Stop-The-World)를 기반으로 하며, 힙 할당이 누적되면 GC가 실행되는 시점에 수십 밀리초의 멈춤이 발생합니다.


Unity는 두 가지 스크립팅 백엔드를 지원합니다.

항목MonoIL2CPP
컴파일 방식JIT (런타임)AOT (빌드 시 C++로 변환)
GC 방식Boehm GCBoehm GC (동일)
성능개발 빌드에 적합배포 빌드 권장
플랫폼 지원PC·에디터iOS·콘솔 필수

GC가 실행되면 모든 관리형 스레드가 일시 정지됩니다 (Stop-The-World). 힙 할당을 줄이는 것이 근본적인 해결책입니다.


// ❌ int → object boxing 발생
object boxed = 42;
Dictionary<string, object> data = new();
data["hp"] = 100; // 매 프레임 boxing
// ✅ 제네릭 사용
Dictionary<string, int> data = new();
data["hp"] = 100;
// ❌ 매 프레임 새 string 생성
void Update()
{
debugText.text = "HP: " + currentHp; // 16~32B 할당
}
// ✅ StringBuilder 또는 TryFormat 사용
readonly System.Text.StringBuilder _sb = new();
void Update()
{
_sb.Clear();
_sb.Append("HP: ");
_sb.Append(currentHp);
debugText.text = _sb.ToString();
}
// ❌ 매 호출 시 IEnumerable 래퍼 할당
var aliveEnemies = enemies.Where(e => e.IsAlive).ToList();
// ✅ for 루프로 대체
var aliveEnemies = new List<Enemy>(enemies.Count);
for (int i = 0; i < enemies.Count; i++)
if (enemies[i].IsAlive) aliveEnemies.Add(enemies[i]);
// ❌ 클로저 캡처 시 힙에 클래스 인스턴스 생성
float threshold = 10f;
enemies.Sort((a, b) => a.Distance(threshold).CompareTo(b.Distance(threshold)));
// ✅ IComparer 구조체로 할당 제거
struct DistanceComparer : IComparer<Enemy>
{
public float threshold;
public int Compare(Enemy a, Enemy b) =>
a.Distance(threshold).CompareTo(b.Distance(threshold));
}
enemies.Sort(new DistanceComparer { threshold = 10f });
// ❌ ArrayList foreach → IEnumerator 박싱
ArrayList list = new ArrayList();
foreach (object item in list) { }
// ✅ List<T> 사용
List<Enemy> list = new List<Enemy>();
foreach (Enemy e in list) { }

Unity 2021부터 UnityEngine.Pool 네임스페이스에 공식 풀 API가 포함됩니다.

using UnityEngine;
using UnityEngine.Pool;
public class BulletPool : MonoBehaviour
{
[SerializeField] GameObject bulletPrefab;
IObjectPool<GameObject> _pool;
void Awake()
{
_pool = new ObjectPool<GameObject>(
createFunc: () => Instantiate(bulletPrefab),
actionOnGet: obj => obj.SetActive(true),
actionOnRelease: obj => obj.SetActive(false),
actionOnDestroy: Destroy,
collectionCheck: false, // 중복 반환 체크 (에디터 디버깅용)
defaultCapacity: 20,
maxSize: 100
);
}
public GameObject Get() => _pool.Get();
public void Release(GameObject obj) => _pool.Release(obj);
}

총알이 화면 밖으로 나가거나 충돌하면 Destroy 대신 pool.Release(obj)를 호출합니다.


Unity 2019.1부터 Incremental GC를 지원합니다. Stop-The-World 대신 GC 작업을 여러 프레임에 분산합니다.

설정 방법: Project Settings → Player → Configuration → Use Incremental GC 체크

// 스크립트에서 GC 시간 제한 설정 (ms)
UnityEngine.Scripting.GarbageCollector.incrementalTimeSliceNanoseconds = 3_000_000; // 3ms

Incremental GC는 스파이크를 완전히 없애지 않습니다. 근본 해결은 할당 자체를 줄이는 것입니다.


Unity Memory Profiler 패키지를 설치하면 힙 스냅샷을 찍어 분석할 수 있습니다.

Window → Memory Profiler → Capture New Snapshot
분석 항목확인 내용
Managed Heap전체 힙 크기 및 단편화
Top Objects by Size메모리를 가장 많이 사용하는 객체
Duplicated Assets중복 로드된 텍스처·메시
Native ObjectsRenderTexture·AudioClip 등

증상원인해결
Update마다 0.1~0.5KB 할당string + 또는 new ListStringBuilder / 풀 사용
씬 전환 시 수백 MB 스파이크이전 씬 오브젝트 미해제Resources.UnloadUnusedAssets()
GetComponent 매 프레임 호출캐싱 누락Awake에서 캐싱
Coroutine yield return new WaitForSeconds매 실행마다 WaitForSeconds 인스턴스 생성캐싱 static readonly WaitForSeconds
// ❌ 매 코루틴 호출마다 할당
yield return new WaitForSeconds(1f);
// ✅ 재사용
static readonly WaitForSeconds Wait1Sec = new WaitForSeconds(1f);
yield return Wait1Sec;