
자바스크립트는 눈에 보이지 않는 곳에서 메모리를 관리하며, 사용되지 않는 데이터를 자동으로 삭제하는 가비지 컬렉션 기능을 제공한다. 이 글에서는 가비지 컬렉션의 동작 원리와 최적화 방법에 대해 알아본다.
자바스크립트는 도달 가능성(reachability)이라는 개념을 기반으로 메모리를 관리한다.
도달 가능한 값은 프로그램이 직접적으로 접근할 수 있는 값을 의미하며, 이러한 값은 삭제되지 않는다.
다음 값들은 기본적으로 도달 가능하며 삭제되지 않는다:
이러한 값은 루트(root)라고 부르며, 루트가 참조하거나 루트를 통해 접근 가능한 값은 모두 도달 가능한 값으로 간주된다.
객체의 프로퍼티가 다른 객체를 참조할 경우, 참조된 객체도 도달 가능한 값이 된다.
자바스크립트 엔진은 이러한 참조 관계를 기반으로 도달 불가능한 값을 찾아내 삭제한다.
let user = { name: "John" }; // 객체 참조
user = null; // 참조 제거
위 코드에서 user는 더 이상 { name: "John" } 객체를 참조하지 않는다. 이로 인해 객체는 도달 불가능한 상태가 되고, 가비지 컬렉터에 의해 삭제된다.
객체가 두 개 이상의 변수에 의해 참조될 때는, 모든 참조가 삭제되어야만 가비지 컬렉션 대상이 된다:
let user = { name: "John" };
let admin = user;
user = null; // admin이 여전히 참조하고 있음
admin = null; // 이제 객체는 도달 불가능해짐
객체들이 서로를 참조하는 구조도 가비지 컬렉션에 영향을 미친다.
function marry(man, woman) {
man.wife = woman;
woman.husband = man;
return { father: man, mother: woman };
}
let family = marry({ name: "John" }, { name: "Ann" });
위 코드에서는 family 객체가 man과 woman 객체를 참조하며, 두 객체는 서로를 참조한다.
delete family.father;
delete family.mother.husband;
두 참조를 삭제하면 John은 도달 불가능한 상태가 된다.
도달 가능성은 외부에서 들어오는 참조에 의해 결정되며, 내부 참조는 영향을 주지 않는다.
여러 객체가 서로를 참조하며 고립된 구조를 형성하는 경우, 해당 구조 전체가 삭제 대상이 된다:
family = null; // family 객체가 모든 참조를 잃음
이로 인해 연결된 모든 객체(John, Ann)가 메모리에서 제거된다.
가비지 컬렉션의 기본 알고리즘인 Mark-and-Sweep은 다음 단계를 따른다:
1. 루트 객체를 수집하고 이를 mark한다.
2. 루트에서 참조하는 모든 객체를 mark한다.
3. mark된 객체가 참조하는 다른 객체도 계속 mark한다.
4. 루트에서 도달 가능한 모든 객체를 방문한 뒤, mark되지 않은 객체를 삭제한다.