본 포스팅은 여기에 올라온 게시글을 바탕으로 작성되었습니다.
파트와 카테고리 동일한 순서로 모든 내용을 소개하는 것이 아닌, 몰랐거나 새로운 내용 위주로 다시 정리하여 개인공부 목적으로 작성합니다.
중간중간 개인 판단 하에 필요하다고 생각될 시, 기존 내용에 추가로 보충되는 내용이 있을 수 있습니다.
자바스크립트는 일일이 사용이 끝난 값은 할당을 해제하고 사용할 값은 할당하는 등의 메모리를 직접 관리할 필요가 없다. 자바스크립트 엔진 내에서 가비지 컬렉터(Garbage Collector)가 끊임없이 동작하며 메모리와 관련된 최적화를 수행해준다.
원시값, 객체, 함수 등 자바스크립트로 구현하는 모든 값은 어느 시점에 결국 메모리의 특정 공간을 차지한다. 해당 값의 사용이 끝났거나, 쓸모없어진 경우에도 이 값들을 계속 점유하도록 놔둔다면 메모리의 사용량이 매우 늘어나고, 새로운 값을 할당하기 위한 자리가 부족할 수도 있다.
따라서 가비지 컬렉터는 쓸모 없어진 값들을 검사하여 메모리로부터 할당을 해제하는 작업을 알아서 수행한다. 이때 자바스크립트는 도달 가능성(Reachability)라는 개념을 사용해 메모리 관리를 수행한다.
도달이 가능하다는 것은 어떻게든 접근이 가능하거나 사용할 수 있는 값을 의미하며, 이러한 값은 메모리에서 제거되지 않는다.
위 목록은 태생부터 항상 도달 가능성이 보장되기 때문에 명백한 이유가 없다면 가비지 컬렉터에 의해 제거되지 않는다. 이러한 값들을 보통 루트(root)라고 칭한다.
또한 이러한 루트가 참조하는 값 또는 체이닝으로 루트에서 참조할 수 있는 모든 값들 역시 도달 가능한 값이 된다. 때문에 이러한 참조가 루트에 연결되어 있다면 이 역시 제거되지 않는다.
가령 아래 이미지와 같은 객체가 생성되있다고 가정하자.
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
이때 만약 다음 두 개의 참조를 제거하게 되면 루트에서 빨간 테두리 영역의 객체로는 더 이상 접근 불가한 상태가 된다. 따라서 가비지 콜렉터에 의해 메모리 최적화가 이루어질 때 해당 객체의 공간을 해제될 것 이다.
delete family.father;
delete family.mother.husband;
아직 husband
였던 객체에서 mother
객체를 wife
로 참조할 수 있지만 제일 중요한건 루트(global
)에서는 해당 객체를 참조할 수 없다는 점이다. 즉 도달 가능한 상태가 아니므로 제거된다.
자바스크립트 엔진에서 가비지 컬렉터가 수행하는 내부 알고리즘을 mark-and-sweep
이라고 한다. 도달 가능한 객체를 마크(mark)하고 그 외의 값은 제거(sweep)하는 개념이다. 이 과정은 그래프 탐색 알고리즘인 DFS와 상당히 유사하다. 루트값으로부터 출발하여 방문할 수 있는 모든 값들을 방문하며 마킹하고, 마킹이 되지 않는 값들은 도달할 수 없는 값으로 판단하여 제거한다. 위 과정을 그림으로 나타내면 아래와 같다. 빨간 테두리는 도달이 불가능 하기때문에 가비지 컬렉터에 의해 제거된다.
자바스크립트 엔진은 최대한 실행에 영향을 적게 미치기 위해 다양한 최적화 기법을 적용한다. 이에 대한 간단한 설명은 다음과 같다.
incremental collection(점진적 수집)
idle-time collection(유휴 시간 수집)
가비지 컬렉션의 동작 방식은 브라우저의 자바스크립트 엔진에 따라 모두 상이하다. 그러나 2012년 기준으로 모든 최신 브라우저들은 가비지 컬렉션에서
mark-and-sweep
알고리즘을 사용하는 것으로 알려져있다.
자바스크립트는 동적 언어 이면서 메모리 관리 역시 자동으로 수행되는데, 아직 명시적으로 또는 프로그래밍 방식으로 가비지 컬렉션을 작동할 수 있는 방법은 없다고 한다.