쓰레기 수집 (컴퓨터 과학)

쓰레기 수집(, GC)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다. 영어를 그대로 읽어 가비지 컬렉션이라 부르기도 한다. 1959년 무렵 리스프의 문제를 해결하기 위해 존 매카시가 개발하였다.
개요.
쓰레기 수집은 동적 할당된 메모리 영역 가운데 더 이상 사용할 수 없게 된 영역을 탐지하여 자동으로 해제하는 기법이다. 더 이상 사용할 수 없게 된 영역이란, 어떤 변수도 가리키지 않게 된 영역을 의미한다.
자바, C#, 그리고 일부 스크립트 언어들은 처음부터 쓰레기 수집 기법을 염두에 두고 설계되어, 언어 정의에 쓰레기 수집이 포함되어 있다. C, C++ 등의 프로그래밍 언어는 수동 메모리 관리를 가정하고 설계되었으나, 쓰레기 수집을 지원하는 구현도 존재한다. D와 같은 어떤 언어들은 쓰레기 수집을 지원하지만, 필요에 따라 쓰레기 수집을 하지 않고 수동으로 메모리를 관리할 수 있다.
장단점.
쓰레기 수집이 지원되는 환경에서는 프로그래머가 동적으로 할당한 메모리 영역의 전체를 완벽하게 관리할 필요가 없어진다. 쓰레기 수집은 다음과 같은 버그를 줄이거나 완전히 막을 수 있다.
반면, 쓰레기 수집 기법은 다음과 같은 단점을 갖고 있다.
포인터 추적 방식.
대부분의 쓰레기 수집 기법은 포인터 추적 방식을 사용한다. 포인터 추적 방식은 한 개 이상의 변수가 접근 가능한 메모리는 앞으로 사용할 수 있는 메모리로 간주하고, 그 밖의 메모리를 해제하는 방식을 가리킨다.
접근 가능한 객체.
접근 가능한 객체는 어떤 변수가 직접 가리키는 메모리, 또는 간접적으로 가리키는 메모리를 의미한다. 접근 가능한 메모리는 다음과 같이 재귀적으로 정의할 수 있다.
여러 가지 포인터 추적 기법.
포인터 추적 기법에는 여러 가지 변종이 존재한다. 어떤 언어들은 다음 기법들 가운데 여러 가지 전략을 함께 사용하기도 한다.
표시하고 쓸기 (mark and sweep).
표시하고 쓸기 기법은 포인터 추적 기법 가운데 가장 단순한 기법이다. 먼저 각 메모리 할당 영역에 표시를 위해 1 비트의 메모리를 남겨 둔다. 표시 단계에서, 모든 변수가 가리키는 영역을 "사용 중"으로 표시하고, 그 영역에서 가리키는 또다른 영역 또한 "사용 중"으로 표시한다. 이와 같이 모든 메모리 영역을 표시하고 나면, 표시되지 않은 영역을 접근 불가능한 메모리 영역이 된다. 접근 불가능한 메모리 영역들을 쓸기 단계에서 모두 해제한다.
이 기법의 단점은, 표시 단계에서 메모리 내용이 변경되지 않아야 하기 때문에 전체 시스템의 실행이 정지된다는 것이다. 또한 전체 메모리 영역을 검사해야 하므로 메모리 페이징을 사용하는 운영체제에서 프로그램의 성능이 저하될 수 있다.
삼색 표시 기법.
표시하고 쓸기 기법의 단점을 보완하기 위해, 많은 언어들은 삼색 표시 기법을 사용한다. 삼색 표시 기법은 기본적으로 표시하고 쓸기와 같은 기법이지만, 표시 단계에서 2가지가 아닌 3가지(흰색, 회색, 검은색) 정보 중 하나로 메모리를 표시한다. 이 기법은 다음과 같은 순서로 이루어진다.
이 알고리즘은 단순한 표시하고 쓸기 알고리즘과 달리, 프로그램이 실행 중에도 병행하여 수행할 수 있다. 또한, 메모리가 고갈되었을 때 쓰레기 수집을 실행하는 것이 아니라 주기적으로 수집하는 것도 가능하다.
객체 이동 기법.
객체 이동 기법은, 해제할 객체 표시가 완료된 후 해제되지 않은 객체를 그대로 두는 것이 아니라, 다른 영역으로 복사하는 기법을 가리킨다. 원래대로 유지해도 무방한 객체를 복사하는 것은 언뜻 비효율적으로 여겨질 수도 있으나, 다음과 같은 실용적인 장점을 가지고 있다.
반면, 메모리 이동 기법은 주기적으로 포인터의 내용이 바뀌므로 포인터 연산을 사용할 수 없게 된다는 단점이 있다.
세대 단위 쓰레기 수집.
많은 연구자들은 프로그램에서 새롭게 할당된 영역일수록 금방 해제될 확률이 높다는 관찰을 보고하였다. 세대 단위 쓰레기 수집 기법은 이런 특성을 이용하여, 각각의 객체를 할당된 시간에 따라 세대별로 구분하여, 각 세대별로 서로 다른 메모리 영역에 객체를 할당한다. 만약 한 세대의 메모리 영역이 꽉 차면, 이 메모리 영역에서 살아남은 객체를 더 오래된 메모리 영역으로 옮긴다. 새로 할당된 영역에서는 대부분의 객체들이 빠르게 해제되고 오래된 영역에서는 객체들이 변하지 않을 확률이 높으므로, 이 기법은 메모리의 일부 영역만을 주기적으로 수집하게 되는 장점이 있다. 자바, 닷넷 프레임워크 등 현대적 언어들은 대부분 이 기법을 사용한다.
참조 횟수 계산 방식.
일부 쓰레기 수집 기법은 참조 횟수 계산 방식을 사용한다. 참조 횟수 계산 방식은 각 객체에서 참조 횟수를 기억하여, 참조 횟수가 0이 되면 해당 객체를 해제하는 방식을 가리킨다. 파이썬 표준 구현인 CPython에서 이 방식을 사용한다. C++에서는 스마트 포인터라는 특수한 객체를 이용해 이 기법을 구현할 수 있다.
참조 횟수 계산 방식은 다음과 같은 장점을 가지고 있다.
위와 같은 장점 때문에, 참조 횟수 계산 방식은 메모리 관리 뿐 아니라 다른 자원 할당 기법에도 종종 사용된다. 예를 들어 하드 디스크 블록의 할당과 해제를 담당하는 파일시스템의 경우, 포인터 추적 방식의 쓰레기 수집은 디스크라는 매체의 특성 상 오랜 시간을 소모하게 된다. 그러나 참조 횟수 계산 방식은 할당된 블록을 해제하는 시점에 해당 블록을 가리키는 포인터가 운영체제의 버퍼에 로딩되어 있으므로, 빠르게 블록을 해제할 수 있다.
반면 참조 횟수 계산 방식에는 다음과 같은 단점이 있다.
쓰레기 수집 기법을 사용하는 언어.
많은 현대 언어들이 쓰레기 수집 기법을 사용한다. C와 C++는 쓰레기 수집 기법을 사용하지 않지만, 라이브러리를 통해 쓰레기 수집 기법을 사용할 수 있다.
ML, 하스켈, 얼랭 등 대부분의 함수형 프로그래밍 언어는 쓰레기 수집 기법을 지원한다. 특히 리스프는 쓰레기 수집 기법을 최초로 사용한 언어이다.
자바, 스몰토크, 자바스크립트 등의 객체지향 언어들은 대부분 쓰레기 수집이 내장되어 있다. 그러나 델파이와 C++는 쓰레기 수집을 사용하지 않는다. 오브젝티브-C는 쓰레기 수집을 사용하지 않았으나, OS X에서 구현된 ObjC 2.0에서는 애플에서 내부적으로 개발한 쓰레기 수집기가 사용된다. 루비 등의 일부 동적 타입 언어 또한 쓰레기 수집을 지원하며, 파이썬, PHP 등의 언어는 참조 횟수 계산 방식을 사용한다.
프로그래밍 초보자를 위해 설계된 베이직 등의 언어 또한 까다로운 메모리 관리를 자동으로 지원하기 위해 쓰레기 수집 기법을 사용한다. 초창기 마이크로컴퓨터에서 구현된 베이직의 경우 쓰레기 수집이 이뤄지는 동안 프로그램이 오랫동안 정지하는 등의 동작을 보였다.