본문 바로가기

Unity

[Unity] Memory leak 유형 및 대처

개요

프로그래머가 꼼꼼하게 챙겨주지 않으면 생각보다 많은 곳에서 메모리 누수가 발생할 수 있다.
액션 RPG 프로젝트를 진행하면서 겪었던 메모리 누수 현상들에 대해 정리하여 기록으로 남기고자 글을 작성한다.
 

발생할 수 있는 상황들

 

1. Singleton static instance

더 이상 보호되지 않아야 할 싱글턴 객체의 OnDestroy 함수에서 반드시 instance = null; 처리를 해 주어야 한다.
이것을 해주지 않으면, OnDestroy 이후에도 객체는 계속해서 alive 한 상태가 된다.
 
결국 메모리에서 해제되지 않으므로, 늘 유념해야 한다.
아예, OnDestroy 함수를 먼저 만들어 두고, instance = null; 코드를 삽입하는 습관을 들이는 것이 좋다.
 

2. delegate

A 객체의 delegate에 B 객체의 event를 등록한다.
B 객체를 파괴시키려 할 때, A 객체에 등록해 둔 event를 해제(unregister)해 주지 않으면, 
A 객체가 파괴되지 않는 한 B 객체도 파괴되지 않는다.
 
따라서, B 객체 파괴시 자신의 모든 event가 제대로 등록해 둔 모든 delegate에서 해제되는지를 보장해 주어야 한다.
 

3. Texture

Prefab이 텍스쳐를 직접 물고 있고, prefab을 Scene이 물고 있을 경우 Scene에서 나와도 텍스쳐가 해제되지 않는다.
(유니티 Texture와 NGUI의 UITexture 모두)
 
직접 링크 방식이 아닌, resource의 path(경로) 정보로 Resource.LoadResource를 통해 로드하는 방식으로 바꾸어야 한다.
이것만으로도, prefab이 파괴될 때 자연스럽게 해결된다.
 
하지만, 파괴되지 않고 disable 처리만 되는 경우 OnDisable에서 Texture 또는 UITexture 객체에 직접 null을 대입해 주어야 한다.
 

4. Material

Material의 경우 참조를 하고 있던 것이 있다면, 해당 객체가 파괴될 때 반드시 material의 참조를 끊어주어야 한다.
변수에 직접 참조하고 있다면 null을 대입, 자료구조에 담고 있다면 반드시 그것을 비워주어야 한다.

 

예를 들어, 다음과 같이 Material을 자료구조에서 사용하고 있었다면...
private readonly Dictionary<Renderer, List<Material>> _originMaterials = new Dictionary<Renderer, List<Material>>();
private readonly List<Material> _cachedMaterials = new List<Material>();
 
다음과 같이 파괴될 때 자료구조를 확실히 비워주도록 하자.
void OnDestroy()
{
    _originMaterials.Clear();
    _cachedMaterials.Clear();
}
 

5. AnimationClip

 
특정 객체가 애님클립을 명시적으로 로드하여 사용하였다면, 객체 파괴시 UnloadAsset을 명시적으로 호출해 주어야 한다.
void OnDestroy()
{
    Resource.UnloadAsset(/*해당 animationClip*/);
}
 

6. Resources.UnloadUnusedAssets

 
Scene에 진입하거나, scene에서 나올 때, 사용되지 않는 리소스를 unload 해 주는 함수.
이 녀석을 제대로 호출해 주지 않으니, scene에서 나올 때 불필요한(더 이상 사용하지 않는) 리소스가 계속 로드되어 있더라.
 
주요 Scene에 들어갔다가 나올때, 위의 함수를 호출하여 이를 해결하였다.
이전 프로젝트에서는 다음과 같은 상황에서 호출하였다.
  • 애셋번들 모두 다운로드 받고 타이틀 화면 진입할 때
  • 로비에 진입할 때
  • 전투에 진입하고 나올 때
  • LoadLevelAsync 후에 
  • 일반적인 scene이 모두 로딩된 후에...
 
다소 중구난방으로 호출한 느낌이 적지 않으나, 확실한 것은 적절할 때 꼭 호출해 주어야 한다는 것이다.
 

결론

개요에서도 적었지만, 프로그래머가 꽤 꼼꼼하게 메모리 누수에 대해 방어를 해 주어야 한다.
 
메모리 누수라는 것이 발생하면 생각보다 찾고 수정하는 과정이 쉽지 않다.
 
  • 메모리가 늘어남이 프로파일러를 통해 관찰되어야 한다. (이래야 쉽다)
  • 큰 카테고리 차원에서 어느 카테고리의 자원이 누수되는지 찾아야 한다.
  • 해당 카테고리 내에서 어떤 항목 (Ex. material, anim...)이 늘어나는지 찾아야 한다.
  • 항목 내 정확하게 어떤 녀석인지 찾아야 한다.
따라서, 사전 예방이 무엇보다 중요하며 마일스톤 단위로 반드시 누수 체크를 귀찮더라도 하라고 얘기하고 싶다.