자동 메모리 관리
업데이트: 2007년 11월
자동 메모리 관리는 관리되는 실행을 수행하는 중에 공용 언어 런타임에서 제공되는 서비스 중 하나입니다. 공용 언어 런타임의 가비지 수집기는 응용 프로그램의 메모리 할당과 해제를 관리합니다. 즉, 관리되는 응용 프로그램을 개발할 때 개발자는 메모리 관리 작업을 수행하기 위해 코드를 작성할 필요가 없습니다. 자동 메모리 관리를 사용하면 실수로 개체 비우기를 수행하지 않거나 메모리 누수를 유발하거나 또는 이미 비워진 개체를 찾기 위해 메모리에 액세스하려는 경우 등의 일반적인 문제를 해결할 수 있습니다. 이 단원에서는 가비지 수집기에서 메모리를 할당하고 해제하는 방법에 대해 설명합니다.
메모리 할당
사용자가 새 프로세스를 시작하면 런타임에서는 인접한 주소 공간 영역을 이 프로세스에 예약합니다. 이 예약된 주소 공간을 관리되는 힙이라고 합니다. 관리되는 힙에서는 힙에 있는 다음 개체가 할당될 주소의 포인터를 관리합니다. 초기에 이 포인터는 관리되는 힙의 기본 주소로 설정됩니다. 모든 참조 형식은 관리되는 힙에 할당됩니다. 응용 프로그램에서 참조 형식을 처음으로 만드는 경우, 이 참조 형식에 대한 메모리는 관리되는 힙의 기본 주소로 할당됩니다. 응용 프로그램에서 두 번째 개체를 만드는 경우, 가비지 수집기는 첫 번째 개체와 바로 인접한 주소 공간에 두 번째 개체의 메모리를 할당합니다. 주소 공간을 사용할 수 있다면 가비지 수집기는 이런 방식으로 계속해서 새 개체에 주소 공간을 할당합니다.
관리되는 힙에서 메모리를 할당하면 관리되지 않는 힙에서 메모리를 할당하는 것보다 속도가 더 빠릅니다. 런타임에서는 포인터에 값을 더하여 개체에 메모리를 할당하기 때문에, 스택에서 메모리를 할당하는 속도만큼 빠릅니다. 또한, 연속으로 할당된 새 개체는 관리되는 힙에 인접하여 저장되므로 응용 프로그램에서 개체에 상당히 빠른 속도로 액세스할 수 있습니다.
메모리 해제
가비지 수집기의 최적화 엔진은 수행 중인 할당에 따라 수집을 수행하기에 가장 적합한 시간을 결정합니다. 수집을 수행할 때 가비지 수집기는 응용 프로그램에서 더 이상 사용되지 않는 개체에 대한 메모리를 해제합니다. 가비지 수집기는 응용 프로그램의 루트를 검사하여 더 이상 사용되지 않는 개체를 결정합니다. 모든 응용 프로그램에는 여러 개의 루트가 있습니다. 각 루트는 관리되는 힙에 있는 개체를 참조하거나 Null로 설정됩니다. 응용 프로그램 루트에는 전역 및 정적 개체 포인터, 스레드 스택의 지역 변수 및 참조 개체 매개 변수, 및 CPU 레지스터가 들어 있습니다. 가비지 수집기는 JIT(Just-in-time) 컴파일러 및 런타임에서 관리되는 활성 루트 목록에 액세스할 수 있습니다. 가비지 수집기는 이 목록을 사용하여 응용 프로그램 루트를 검사하고 이 과정에서 그래프를 만드는데, 이 그래프에는 루트에서 연결할 수 있는 모든 개체가 포함되어 있습니다.
그래프에 없는 개체는 응용 프로그램 루트에서 연결할 수 없습니다. 가비지 수집기는 연결할 수 없는 개체를 가비지로 간주하고 이 개체에 할당된 메모리를 해제할 것입니다. 수집을 수행할 때 가비지 수집기는 연결할 수 없는 개체에서 사용되는 주소 공간 블록을 찾기 위해 관리되는 힙을 검사합니다. 연결할 수 없는 개체가 발견되면 가비지 수집기는 메모리 복사 기능을 사용하여 메모리에서 연결할 수 있는 개체를 압축합니다. 그러면 연결할 수 없는 개체에 할당된 주소 공간 블록이 해제됩니다. 연결할 수 있는 개체의 메모리가 압축되면 가비지 수집기는 포인터의 위치를 적절하게 수정합니다. 그러면 응용 프로그램 루트는 개체의 새 위치를 가리킬 수 있습니다. 또한 가비지 수집기는 관리되는 힙의 포인터 위치를 연결할 수 있는 마지막 개체 다음에 지정합니다. 수집을 수행하는 동안 연결할 수 없는 개체의 수가 엄청나게 발견되는 경우에만 메모리를 압축한다는 점에 주목합니다. 수집을 수행한 후에도 관리되는 힙에서 모든 개체가 그대로 남아 있다면 메모리 압축을 수행할 필요가 없습니다.
런타임에서는 성능 향상을 위해 큰 개체의 메모리를 별도의 힙에 할당합니다. 그러면 가비지 수집기는 큰 개체에 할당된 메모리를 자동으로 해제합니다. 하지만 메모리에서 큰 개체가 이동하는 것을 피하기 위해 이 메모리는 압축하지 않습니다.
세대 및 성능
가비지 수집기의 성능을 최적화하기 위해 관리되는 힙을 0, 1, 2의 세 가지 세대로 구분합니다. 런타임의 가비지 수집기 알고리즘은 컴퓨터 소프트웨어 업계의 가비지 수집 체계 실험을 통해 밝혀진 일반 사실을 기반으로 하고 있습니다. 첫째로, 가비지 수집기는 관리되는 전체 힙보다 관리되는 일부 힙에서 더 빠르게 메모리를 압축할 수 있습니다. 둘째로, 개체가 새로울수록 수명은 더 짧아지고 개체가 오래될수록 수명은 더 길어집니다. 마지막으로, 새로운 개체일수록 서로 연결되는 경향이 있어서 응용 프로그램에서 거의 동시에 액세스됩니다.
런타임 가비지 수집기는 새 개체를 세대 0에 저장합니다. 응용 프로그램 수명의 초기에 만들어진 개체 중에서 수집 후에도 남아 있는 개체는 승격되어 세대 1과 2에 저장됩니다. 개체 승격 과정은 이 단원의 마지막 부분에서 설명하고 있습니다. 전체 힙보다 일부 관리되는 힙을 압축하는 것이 더 빠르기 때문에 가비지 수집기는 수집을 수행할 때마다 이 체계를 사용하여 전체 관리되는 힙에서 메모리를 해제하는 대신 특정 세대에서 메모리를 해제할 수 있습니다.
실제로 가비지 수집기는 세대 0이 가득차면 수집을 수행합니다. 응용 프로그램에서 새 개체를 만들려고 할때 세대 0이 가득찬 경우, 가비지 수집기에서는 개체 할당할 주소 공간이 세대 0에 존재하지 않음을 감지합니다. 그러면 가비지 수집기는 이 개체를 위해 세대 0에서 주소 공간을 비우기 위해 수집을 수행합니다. 가비지 수집기는 관리되는 힙에서 모든 개체를 검사하는 대신 먼저 세대 0에서 개체를 검사합니다. 이 방식은 가장 효율적인 방식입니다. 왜냐하면 새 개체의 수명은 짧은 성향이 있으며 수집이 수행되면 세대 0의 상당수 개체는 더 이상 응용 프로그램에서 사용되지 않을 것이기 때문입니다. 또한, 세대 0에만 수집을 수행하더라도 응용 프로그램에서 새 개체를 계속 만들 수 있는 충분한 메모리를 확보하기도 합니다.
가비지 수집기는 세대 0에서 수집을 수행한 후 이 단원의 메모리 해제에서 설명된 것처럼 연결할 수 있는 개체의 메모리를 압축합니다. 그런 다음 가비지 수집기는 이 개체를 승격시켜서 이 부분의 관리되는 힙을 세대 1로 간주합니다. 수집 후에도 존재하는 개체는 수명이 긴 성향이 있기 때문에, 이런 개체를 상위 세대로 승격시키는 것이 당연합니다. 이 결과 가비지 수집기에서 세대 0 수집을 수행할 때마다 세대 1과 2에서 개체를 다시 검사할 필요가 없습니다.
가비지 수집기는 세대 0에 처음으로 수집을 수행한 다음 연결할 수 있는 개체들을 세대 1로 승격시키지만, 아직도 관리되는 힙에 남아있는 개체들은 세대 0으로 간주합니다. 가비지 수집기는 세대 0이 가득차거나 다른 수집을 수행할 필요가 있을 때까지 세대 0에서 새 개체에 대한 메모리 할당을 계속합니다. 이 시점에서 가비지 수집기의 최적화 엔진은 이전 세대에서 개체를 검사할 필요가 있는지를 결정합니다. 예를 들어, 세대 0에서 수집을 수행했음에도 불구하고 응용 프로그램에서 새 개체를 성공적으로 만드는 데 필요한 충분한 메모리를 확보할 수 없는 경우, 가비지 수집기는 세대 1에서 세대 0의 순서로 수집을 수행할 수 있습니다. 여기에서도 충분한 메모리를 확보할 수 없다면, 가비지 수집기는 세대 2, 1, 0의 순서로 수집을 수행할 수 있습니다. 각 수집이 완료되면 가비지 수집기는 세대 0에 있는 연결할 수 있는 목록을 압축하여 세대 1로 승격시킵니다. 또한, 수집이 완료된 후에 세대 1에 존재하는 개체를 세대 2로 승격시킵니다. 가비지 수집기는 세 가지 세대만을 지원합니다. 따라서 수집이 완료된 후에 세대 2에 존재하는 개체는 상위 세대로 증가시킬 없으므로, 다음 수집에서 연결할 수 없는 개체로 결정될 때까지 세대 2에 보관됩니다.
관리되지 않는 리소스의 메모리 할당 해제
가비지 수집기는 사용자 응용 프로그램에서 만들어지는 대부분의 개체에 대해 메모리 관리 작업을 자동으로 수행할 수 있습니다. 하지만 관리되지 않는 리소스의 경우는 명시적으로 정리할 필요가 있습니다. 가장 일반적인 형태의 관리되지 않는 리소스로는 파일 핸들, 창 핸들 또는 네트워크 연결 등의 운영 체제 리소스를 래핑하는 개체를 들 수 있습니다. 가비지 수집기에서는 관리되지 않는 리소스를 캡슐화하는 데 사용되는 관리되는 개체의 수명을 추적할 수 있지만, 리소스 정리 방법에 대한 구체적인 정보는 알 수 없습니다. 관리되지 않는 리소스를 캡슐화해 주는 개체를 만드는 경우 관리되지 않는 리소스를 정리하는 데 필요한 코드를 공용 Dispose 메서드에 제공하는 것이 좋습니다. 개체 사용자는 Dispose 메서드를 사용하여 메모리 할당을 명시적으로 해제할 수 있습니다. 관리되지 않는 리소스를 캡슐화해 주는 개체를 사용하는 경우 사용자는 Dispose 메서드를 알아 두고 필요한 경우 이 메서드를 호출해야 합니다. 관리되지 않는 리소스를 정리하는 방법과 Dispose를 구현하는 디자인 패턴 예제에 대해서는 가비지 수집을 참조하십시오.