참조 카운팅(Reference-Counting) 가비지 컬렉션이란?
참조 카운팅 방식은 가장 단순한 형태의 가비지 컬렉션(Garbage Collection) 알고리즘입니다. 이 알고리즘의 핵심 원리는 더 이상 어떤 곳에서도 참조되지 않는 객체를 찾아내어 메모리에서 해제하는 것입니다.
객체에 연결된 참조가 하나도 남아 있지 않다면, 해당 객체는 가비지 컬렉션의 대상이 됩니다. 즉, 프로그램 어디에서도 그 객체에 접근할 수 없게 되면 메모리를 회수할 수 있는 것입니다.
기본 동작 예제
var obj = {
x: { y: 2 }
};
// 두 개의 객체가 생성되었습니다.
// 하나는 다른 객체의 속성(property)으로 참조되고 있습니다.
// 따라서 어느 쪽도 가비지 컬렉션 대상이 되지 않습니다.
obj = 1;
// 원래 obj가 가리키던 객체의 'x' 속성이 참조하던 내부 객체는
// 이제 참조 개수가 0이 되었으므로 가비지 컬렉션될 수 있습니다.위 예제에서 obj 변수가 새로운 값(숫자 1)을 할당받으면, 기존에 참조하던 객체와 그 내부의 x 속성이 가리키던 객체 중 외부 참조가 사라진 객체는 회수 대상이 됩니다.
참조 카운팅 방식의 한계점
참조 카운팅 알고리즘은 간단하지만, 순환 참조(Cycle) 상황에서 심각한 한계를 보입니다. 순환 참조란 두 개 이상의 객체가 서로를 참조하여 마치 고리처럼 연결된 구조를 말합니다.
순환 참조 문제 예제
아래 예제에서는 두 개의 객체가 생성된 후 서로를 참조하여 순환 참조를 만듭니다. 함수 호출이 끝나면 두 객체는 모두 스코프(scope)를 벗어나므로 실질적으로 더 이상 사용되지 않으며, 이론적으로는 메모리에서 해제되어야 합니다.
그러나 참조 카운팅 알고리즘은 두 객체 각각이 최소 한 번씩은 참조되고 있다고 판단하기 때문에, 둘 중 어느 것도 가비지 컬렉션 대상으로 인식하지 않습니다. 결과적으로 이 메모리는 프로그램 실행이 끝날 때까지 해제되지 않은 채 누수(memory leak)가 발생하게 됩니다.
function f() {
var obj1 = {};
var obj2 = {};
obj1.p = obj2; // obj1이 obj2를 참조
obj2.p = obj1; // obj2가 obj1을 참조 → 순환 참조(cycle) 발생
}
f();
// 함수 종료 후 obj1과 obj2는 스코프를 벗어나지만,
// 서로를 참조하고 있으므로 참조 카운트가 0이 되지 않아
// 가비지 컬렉션이 이루어지지 않습니다.정리
참조 카운팅 가비지 컬렉션은 구현이 간단하고 직관적이라는 장점이 있지만, 순환 참조가 발생하면 메모리를 회수하지 못하는 치명적인 단점을 가지고 있습니다. 이러한 이유로 현대 자바스크립트 엔진(V8, SpiderMonkey 등)은 주로 마크 앤 스윕(Mark-and-Sweep) 알고리즘을 사용합니다. 마크 앤 스윕 방식은 루트(root)에서 시작해 도달 가능 여부를 판단하기 때문에, 서로 참조하는 객체라도 외부에서 접근할 수 없다면 정상적으로 메모리를 회수할 수 있습니다.