Unity 메모리 누수 잡기 — Memory Profiler 실전
Unity의 관리 힙과 네이티브 메모리 문제를 구분하고, 스냅샷 비교와 참조 경로로 누수 의심 대상을 좁히는 절차입니다.
- unity
- performance
- profiler
"메모리 누수"를 하나의 원인으로만 보면 Unity에서 헛다리를 짚습니다. 관리 객체는 예상보다 오래 남은 참조 때문에 회수되지 않을 수 있고, 네이티브 객체·리소스는 별도의 수명 관리가 필요합니다. GC는 더 이상 도달할 수 없는 관리 객체를 정리하지만 네이티브 메모리를 직접 정리하지는 않습니다.
Unity에서 점검할 세 갈래
관리 힙: 이벤트 구독 해제 누락은 대표적인 원인입니다. 특히 static 이벤트는 구독자가 씬을 나간 뒤에도 관리 객체에 대한 참조를 붙잡을 수 있습니다. 클로저가 외부 변수를 캡처해 예상보다 오래 살아남는 경우, List나 Dictionary에 계속 추가만 하고 제거 로직이 없는 경우도 같은 계열입니다.
// ❌ static 이벤트 구독 후 해제 누락 — 구독자가 죽어도 이벤트가 붙잡는다
void OnEnable() => GameEvents.OnScoreChanged += HandleScore;
// OnDisable 에 -= 가 없으면 static 이벤트의 참조가 남을 수 있다
네이티브 메모리: Texture2D, Mesh, RenderTexture, AudioClip 같은 Unity 객체에는 관리 래퍼와 별개인 네이티브 메모리가 있을 수 있습니다. 런타임에 직접 생성해 소유한 객체는 사용이 끝날 때 Destroy() 등 해당 리소스에 맞는 해제 절차를 확인해야 합니다. RenderTexture.Release()는 내부 렌더링 리소스를 반납하지만 객체 자체를 파괴하는 호출은 아닙니다. AssetBundle도 로드한 객체의 사용 범위를 고려해 적절한 시점에 Unload()해야 합니다.
에디터 플레이 모드의 유령: Enter Play Mode Settings에서 domain reload를 끈 프로젝트는 static 상태가 에디터의 플레이 세션 경계를 넘어 살아남습니다. 두 번째 플레이부터 메모리가 계속 늘거나 이상한 예외가 난다면 이 설정을 점검해 보세요. 로그 콜백에서 같은 함정을 다룬 적이 있습니다 — 구독 해제 누락이 만드는 유령을 참고하십시오. 에디터에서만 반복되는 현상인지 실제 빌드에서도 발생하는지는 따로 확인해야 합니다.
Memory Profiler — 증가 원인은 스냅샷을 비교한다
Unity 공식 Memory Profiler는 현재 메모리 구성과 객체를 보여주고, 두 스냅샷을 **비교(diff)**해 증가한 항목을 찾게 해줍니다. 단일 스냅샷도 유용하지만 증가 추세나 누수 여부는 한 장만으로 판정하기 어렵습니다.
절차는 다음과 같습니다.
- 의심되는 동작(씬 전환, 특정 기능 반복 사용)을 하기 전에 스냅샷을 찍는다
- 동일한 동작을 몇 차례 반복한다
- 후 스냅샷을 찍는다
- 두 스냅샷을 비교해 늘어난 항목을 확인한다
동작 한 번으로는 판단하지 마십시오. 첫 실행에는 셰이더 컴파일이나 캐시 초기화처럼 한 번만 발생하는 정상적인 증가가 섞일 수 있습니다. 같은 조건에서 여러 번 반복했는데 살아있는 객체와 사용량이 계속 늘면 누수를 의심하고, 어느 지점에서 증가가 멈춘다면 캐시·풀일 가능성을 살펴보세요. 어느 쪽이든 증가한 객체와 소유 경로를 확인해야 결론을 낼 수 있습니다.
무엇을 붙잡고 있나 — 참조 경로 추적
diff에서 늘어난 객체를 찾았다면 다음 질문은 "누가 이걸 살려두는가"입니다. Memory Profiler의 참조 정보를 따라가면 static 컬렉션, 해제 안 된 이벤트, 씬이 바뀌어도 살아있는 매니저 싱글톤처럼 수명이 긴 소유자를 찾는 데 도움이 됩니다.
경로가 여러 갈래로 갈라져 헷갈린다면, 수명이 예상보다 긴 참조를 먼저 살펴보십시오. 객체가 의도한 시점 이후에도 살아 있는 이유를 확인하는 것이 핵심입니다.
누수인지 캐시인지 판단 기준
늘어나는 메모리가 전부 버그는 아닙니다. 아틀라스 캐시, 오브젝트 풀, 리소스 사전 로딩은 의도적으로 메모리를 들고 있습니다. 아래 패턴은 결론이 아니라 다음 조사 방향입니다.
| 패턴 | 확인할 것 |
|---|---|
| 같은 동작을 반복해도 특정 크기에서 증가가 멈춘다 | 의도한 캐시/풀인지 확인 |
| 씬을 왕복할 때 살아있는 객체 수가 계속 증가한다 | 참조·소유권 경로 조사 |
GC.Collect() 후에도 총 메모리가 그대로다 | 관리 힙 예약 영역이나 네이티브 사용량을 구분; 이것만으로 누수 판정 불가 |
| 씬 언로드 직후엔 줄지만 다음 로드에서 이전 수준을 넘어선다 | 언로드와 재로딩 시 객체 수·보유 경로 비교 |
씬을 여러 번 왕복시키며 같은 시점의 객체 수와 사용량을 기록하면 의심 대상을 빠르게 좁힐 수 있습니다. 계단식 증가나 평탄화는 단서일 뿐이므로 스냅샷의 객체 목록과 참조 경로로 확인하세요.
정리 — 반복 가능한 체크리스트
- 씬을 여러 번 왕복시키며 힙 총량이 우상향하는지 먼저 확인한다
- 의심 동작 전/후 스냅샷을 찍고 diff에서 늘어난 타입을 확인한다
- 늘어난 객체의 참조 경로에서 예상보다 오래 살아남는 소유자를 찾는다
- 관리 힙 문제인지(예:
OnDisable의-=누락) 네이티브 리소스 수명 문제인지 구분한다 - domain reload를 끈 프로젝트라면 에디터 재현이 실제 빌드 증상과 다를 수 있음을 감안한다
메모리 문제가 성능 저하나 크래시로 나타났을 때는 재현 조건과 시간대를 함께 남겨야 합니다. 저희 Rekon은 캡처 시점의 씬·기기·최근 로그를 기록하고, 지원되는 데스크톱 환경에서는 FFmpeg로 영상을 남길 수 있습니다. 메모리 스냅샷을 대신하지는 않지만, 어느 상황에서 이상이 보였는지 좁히는 단서가 됩니다.