목차
메모리 누수(Leak)
메모리 누수(Memory Leak)는 사용을 끝낸 메모리를 적절히 해제하지 않아, 점점 사용할 수 있는 메모리 양이 줄어드는 현상을 말합니다.
게임 개발을 하다 보면 메모리 누수는 반드시 발생하는 이슈라고 생각합니다.
아무리 뛰어난 개발자라 해도 모든 메모리 누수 시점을 동시에 파악하면서 개발하는 것은 불가능합니다.
따라서 메모리 누수를 해결하기 위해서는, 누수가 발생하는 일반적인 예시와 이를 찾는 방법을 알아야 한다고 생각합니다.
메모리 누수가 발생할 수 있는 다양한 예시
메모리 누수는 무수히 많은 경우에서 발생합니다.
사실상, 모든 메모리 누수 상황을 나열하는 것은 불가능합니다. 따라서, 일반적으로 자주 발생하는 상황에 대한 예시를 설명하겠습니다.
오브젝트 풀링의 잘못된 사용
오브젝트 풀링은 메모리를 활용하여 CPU와 GPU의 성능을 향상시키는 방법입니다. 그러나, 과도한 오브젝트 풀링은 메모리 누수의 주요 원인이 될 수 있습니다.
예를 들어 아래와 같은 오브젝트 풀 로직이 있다고 가정해봅시다.
public class ObjectPool {
private List<GameObject> pooledObjects;
public ObjectPool()
{
pooledObjects = new List<GameObject>();
}
public GameObject GetObject("~~~")
{
// 오브젝트 풀 사용
}
public void Despawn(GameObject gameObject)
{
// 풀 반납
}
}
C#
복사
여기서는 GetObject를 사용하여 오브젝트를 활용한 후, Despawn을 사용하여 오브젝트를 풀에 반환했습니다.
이때, 사용하지 않는 오브젝트는 풀에 남아 메모리를 차지하고 있습니다.
여기서 메모리 누수가 발생할 수 있습니다.
해결법 : 이후에, 사용하지 않거나 오랜 시간 동안 사용하지 않는 오브젝트는 특정 시점에 제거하는 것이 좋습니다.
싱글톤의 잘못된 사용
싱글톤 클래스를 사용할 때 유의해야 할 점은 싱글톤 패턴 자체가 메모리 누수를 쉽게 발생시킬 수 있다는 것입니다.
싱글톤 클래스에 선언된 모든 변수는 전역 변수로 선언됩니다.
따라서, 해당 변수에서 사용한 오브젝트를 사용한 후에 별도로 해제하지 않으면, 계속해서 메모리에 남아있게 됩니다.
아래와 같은 싱글톤 코드가 있다고 가정해봅시다.
// 싱글톤 클래스
public class SingletonBehaviour : MonoBehaviour {
// 싱클톤 클래스 구현
~~
//
public GameObject gameObject = null;
}
C#
복사
여기서 gameObject를 외부에서 사용한 후, 초기화를 하지 않았다면, 메모리 누수가 발생합니다.
해결법
1.
싱글톤 클래스를 우선적으로 꼭 사용해야되는지 확인합니다. 여기서는 싱글톤 클래스를 사용함으로써의 유지보수의 용이함과 개발에서의 편의성도 포함됩니다.
2.
싱글톤 클래스에서 사용한 객체들을 사용이 끝난 뒤, 초기화합니다.
이벤트 핸들러의 잘 못된 사용.
일반적으로 유니티에서 이벤트 핸들러(델리게이트, 액션 등) 등의 부분에서도 메모리 누수가 발생할 수 있습니다.
이벤트 핸들러에 이벤트를 등록한 뒤, 해당 이벤트를 제거하지 않는다면 메모리 누수가 발생합니다.
아래와 같은 이벤트 핸들러가 있다고 가정해봅시다.
public class EventPublisher {
public delegate void UpdateEvent();
public event UpdateEvent OnUpdate;
public void Update() {
OnUpdate?.Invoke();
}
}
public class EventSubscriber {
private EventPublisher publisher;
public EventSubscriber(EventPublisher publisher) {
this.publisher = publisher;
publisher.OnUpdate += HandleUpdate; // 이벤트 구독
}
private void HandleUpdate() {
// 업데이트 처리
}
// 메모리 누수: 이벤트 구독 해제를 하지 않음
}
C#
복사
EventSubscriber를 통해 이벤트를 등록한 후에는 이벤트를 사용합니다.
이벤트를 사용한 후, 해당 이벤트를 사용하지 않을 경우에는 반드시 제거해야합니다. 그렇지 않으면 메모리 누수가 발생할 수 있습니다.
해결법
1.
1차적인 해결법은 이벤트를 등록할 때, 우선 해당 이벤트를 제거한 뒤 등록하는 습관을 갖는 것입니다.
public EventSubscriber(EventPublisher publisher) {
this.publisher = publisher;
publisher.OnUpdate -= HandleUpdate; // 이벤트 제거
publisher.OnUpdate += HandleUpdate; // 이벤트 구독
}
C#
복사
2.
당연히 두 번째 방법은 사용한 이벤트 핸들러를 적시에 제거하는 것입니다.
이벤트 핸들러에서 발생하는 메모리 누수는 특히 찾기 어렵습니다. 따라서, 코딩 습관과 이벤트 핸들러 사용 시 메모리 누수에 주의를 기울이며 개발해야 합니다.
리소스와 에셋 관리
해당 문제는 리소스와 에셋을 로드하고 제대로 제거하지 않았을 때 발생합니다.
즉, 리소스 매니저의 설계가 적절하지 않을 경우에 이런 문제가 발생합니다.
텍스쳐를 로드하는 아래와 같은 코드가 있다고 가정합시다.
public class TextureLoader {
private Dictionary<string, Texture> loadedTextures = new Dictionary<string, Texture>();
public Texture GetTexture(string path) {
if (!loadedTextures.ContainsKey(path)) {
var texture = LoadTexture(path); // 텍스쳐 로드
loadedTextures[path] = texture; // 딕셔너리에 저장
}
return loadedTextures[path];
}
// 메모리 누수: 사용하지 않는 텍스쳐를 메모리에서 해제하지 않음
}
C#
복사
위처럼, 리소스를 로드한 후 해당 텍스처를 제대로 해제하지 않는 코드는 매우 흔한 리소스 매니저 설계의 실수입니다. 이로 인해 메모리 누수가 발생합니다.
해결법 : 사실상 위와 같은 문제에는 정확한 해결책이 없습니다. 자주 사용하는 텍스처의 경우, 메모리를 사용하는 것이 적절할 수 있습니다.
그러므로, 리소스 관리가 복잡해지더라도 메모리를 효율적으로 관리할 수 있도록 하는 것이 중요합니다.
예를 들면, A라는 텍스처를 사용한 후에, 개발자가 원하는 특정 시점에 A를 초기화하는 함수를 만드는 것도 한 가지 방법입니다.
코루틴의 잘 못된 사용
비동기 작업이나 코루틴을 사용할 때, 작업이 적절히 종료되지 않으면 메모리 누수가 발생할 수 있습니다.
예를 들어, 아래와 같은 코드가 있다고 가정해봅시다.
IEnumerator ExampleCoroutine() {
var gameObject = new GameObject();
while (true) {
// 무한 루프
yield return null;
}
}
C#
복사
해당 코루틴을 사용한 후, 적절히 종료시키지 않으면, 코루틴에서 사용하는 대부분의 객체는 초기화되지 않을 수 있습니다.
해결법 : 당연히, 적절하게 코루틴을 종료시켜야 합니다.
이 문제는 생각보다 자주 발생 하는 문제입니다. 그래서 코루틴을 사용하는 경우, 다른 Dictionary 등에서 이 코루틴을 추적하면서 특정 시점에 종료할 수 있도록 하는 것도 좋은 방법입니다.
기타 메모리 누수
위의 예시 외에도 메모리 누수가 발생하는 경우는 많습니다. 셰이더 변수 사용이 과도하거나, 씬을 제대로 해제하지 않았거나, 프리팹을 생성한 후 제거하지 않는 경우 등이 많습니다.
이러한 문제는 아래에 소개하는 메모리 누수를 찾는 방법을 통해 해결하는 것이 바람직합니다.
메모리 누수를 찾는 방법
메모리 누수를 찾는 대표적인 방법은 메모리 프로파일러를 사용하여 메모리를 추적하는 것입니다.
다음은 유니티에서 제공하는 메모리 프로파일러를 사용하는 방법에 대한 설명입니다.
개발 환경에서의 유니티의 메모리 프로파일러는 물론, 빌드 이후 iOS의 XCode와 Android의 스튜디오를 통해 메모리를 추적하는 것은 메모리 누수를 찾는 중요한 부분입니다.