C# 메모리 관리 – 주기, 스코프, 가비지 컬렉션 Lifetime, Scope, Garbage Collection

C# 메모리 관리 – 주기, 스코프, 가비지 컬렉션 Lifetime, Scope, Garbage Collection

memory management

 

주기 스코프 그리고 가비지 컬렉션 (Lifetime, Scope and Gabage Collection)

참조 타입 변수의 경우, 주기 lifetime는 가비지 컬렉터의 일이기 때문에 명확하지 않습니다. C 그리고 C++의 경우, 동적으로 할당한 변수를 메모리에서 제거하는 일은 프로그래머의 몫이였습니다. (C에서 free 함수, C++에서 소멸자).

C# 에서는, 가비지 컬렉터가 해당 변수가 사용 중인지 확인하고, 실행 중인 코드에서 변수를 참조하는 곳이 없으면, 가비지 컬렉터가 변수가 차지했던 메모리 위치를 사용 가능한 위치로 표시합니다. COM 인터페이스에 친숙하거나 다른 reference  counting 시스템에 친숙한 프로그래머는 가비지 컬렉션이 이상한 개념으로 다가올 수 있습니다 – 왜나하면 reference counting 시스템에서 두 개의 객체가 서로를 참조할 수 있고, 이로 인해서 메모리가 해제되지 않는 문제를 야기시킬 수 있기 때문입니다 – 하지만 가비지 컬렉션은 그런 문제가 없습니다.

가비지 컬렉션은 아주 큰 주제이며 유니티는 메모리를 회수 처리를 구현하는 데 있어서 몇가지 이상한 점을 가지고 있습니다 – 유니티가 아닌 일반 C#을 사용하셨던 분이라면 짧게 사용했던 작은 객체에 대한 GC 동작이 가볍다고 생각하실텐데, 유니티에서는 그렇지 않습니다. 메모리 블록이 시스템에 채워질 때마다 힙heap 상의 모든 객체의 접근성을 확인해야 합니다 – 이 것은 해당 메모리 블록이 다른 코드에서 접근이 가능한지 또는 실행이 가능한지 여부를 확인한다는 의미 입니다 – 그리고 나서 해당 객체를 유지하거나 메모리를 해제합니다. 일반적인 .NET의 가비지 컬렉션은 제너레이션generation이라고 부르는 개념을 사용해서 접근성을 판별하는 작업을 최소화 합니다. 이 작업은 사용가능한 메모리가 충분하지 않은 경우, 새로 생성된 객체를 첫 번째로 회수하고 그보다 오래된 객체들을 차례로 회수합니다. 하지만 유니티는 항상 모든 객체를 확인하기 때문에 가비지 컬렉션이 주요 오버헤드이며, 일반적인 .Net 언어에서 동작하는 것보다 훨씬 비용이 큽니다.

가비지 컬렉션에서 접근성 확인은 마법과 같은 개념입니다 – 가비지 컬렉터는 이를 이용해서 특정 코드가 실행 중인지, 실행 가능한 지 여부를 결정할 수 있게 됩니다. 이 개념은 함수 내부의 클로저clousure의 사용도 포함됩니다 – 클로저는 매우 복잡한 개념이지만 아주 강력합니다, 하지만 빠르게 동작해야 하는 게임면에서는 그리 빠른 기술은 아닙니다.
클로저는 특전 지역 변수를 참조하거나 함수 내부에 정의된 무명 함수(anonymous function)안의 파라미터를 참조할 때 생성됩니다. 그 변수들의 값이 유지되고 그로인해서 무명 함수가 호출될 때 해당 변수는 이 전 루틴이 실행됐을 때와 동일하게 유지 됩니다.

List<Action> actionsToPerformLater = new List<Action>();

void DisplayAMessage(string message)
{
    var t = System.DateTime.Now;
    actionsToPerformLater.Add(()=>{
        Debug.Log(message + " @ " + t.ToString());
    });
}

void SomeOtherFunction()
{
    DisplayAMessage("Hello");
    DisplayAMessage("World");
    SomeFunctionThatTakesAWhile();
    DisplayAMessage("From Unity Gems");

    foreach(var a in actionsToPerformLater)
    {
        a();
    }
}

이 코드에서 DisplayAMessage 함수를 실행할 때마다 전달되는 메시지를 감싸는 클로저가 형성됩니다. 마지막에 foreach 루프를 실행할 때, 리스트에 메시지를 추가하기 위해서 전에 호출했던 함수에서 전달된 파라미터의 값이 debug 메시지로 출력됩니다. 클로저는 매우 강력한 툴이며 나중에 다룰 예정입니다.

가비지 컬렉션은 특정 동작을 실행하는데 시스템에 메모리가 충분하지 않다고 판단될 때 발생합니다. 다시 말해서, 상당한 기간 동안 폐기된 객체가 회수되지 않을 수 있다는 의미입니다 – 따라서 명시적으로 외부 연결을 끊거나 그렇지 않으면 특정 객체가 더이상 필요하지 않을 때 외부 리소스를 명시적으로 해제하는 것이 일반적입니다. 내부 객체(프로젝트내의 클래스)를 명시적으로 해제할 필요는 없지만, 파일 스트림(File Stream)과 시스템에 영향을 줄 수 있는 데이터 베이스 연결 등을 포함하는 프로젝트에서는 명시적으로 해제해야 할 이유가 있습니다. 예를 들어서, 파일을 열고나서 닫지 않으면, 그 파일은 열려있는 상태로 폐기되지 않았기 때문에 나중에 그 파일에 접근할 수 없습니다.

가비지 컬렉션은 아주 비싼 동작이기 때문에, 레벨을 로딩하거나, 유저가 일시정지 메뉴를 눌렀거나, 그 외의 유저가 알아차리지 못하는 시점 즉, 게임에 영향을 주지 않을 때 수동으로 발생시키는 것이 이해가됩니다. System.GC.Collect(); 함수를 이용해서 수동으로 가비지 컬렉션을 발생시킬 수 있습니다.

참조형 변수는, GameObject.Find()와 같이 적절한 장치를 해놓으면 프로그램 안에서 어디서나 접근이 가능하고 해당 객체의 참조 값을 반환합니다.

 

내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂

RonnieJ

프리랜서 IT강사로 활동하고 있습니다. 게임 개발, C++/C#, 1인 기업에 관심이 많습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Please turn AdBlock off

Notice for AdBlock users

Please turn AdBlock off