C
, C++
등 저수준 언어에서는 메모리 관리를 위해 malloc
, calloc
, realloc
, free
등의 함수를 지원합니다. 하지만 JavaScript
에서는 자동으로 메모리를 할당하고 해제해주는데, 이는 가비지 콜렉션(GC)
에 의해 관리됩니다.
이번 포스트에서는 JavaScript
에서 메모리 관리를 해주는 GC
에 대해 알아보겠습니다.
모든 프로그래밍 언어에서의 메모리 생존주기는 비슷합니다.
2번은 모든 언어에서 명시적이지만, 1번과 3번은 C
같은 저수준 언어에서는 명시적이고 JavaScript
같은 고수준 언어에서는 암묵적으로 작동합니다.
JavaScript
에서는 변수
와 함수
등을 선언할 때 메모리가 자동으로 할당됩니다.
let num = 7; // 숫자 변수 선언
let str = 'value'; // 문자열 변수 선언
const date = new Date(); // Date 객체 변수 선언
const obj = { // 객체 변수 선언
key: 'value',
};
function func(param) { // 함수 선언
return param;
}
메모리 사용은 할당된 메모리의 값을 읽거나 쓰는 동작을 의미합니다. 또한 함수를 호출하는 것도 메모리를 사용하는 것입니다.
console.log(obj.key); // obj 객체의 key 속성 읽기
str = func('new value'); // func 함수를 호출한 결과값을 str 변수에 넣기
메모리가 더이상 사용되지 않는 경우에는 메모리를 해제하게 됩니다. 하지만 JavaScript
에서는 메모리를 개발자가 명시적으로 해제할 수 있는 방법이 없기 때문에 GC
가 해당 작업을 수행해주고 있습니다.
JavaScript
의 GC
는 표시하고 쓸기(Mark-and-Sweep)
알고리즘을 사용하고 있습니다. 이 알고리즘은 메모리에 닿을 수 있는(Reachable)
지 체크하고, 만약 닿을 수 없다
면 메모리 해제의 대상이 됩니다.
이 알고리즘은 roots(전역변수들의 모음)
라는 집합을 가지고 있고, roots
에서 참조할 수 있는 메모리는 닿을 수 있는
메모리로 인지합니다.
let toy = {
price: '$30',
color: 'blue',
};
toy
라는 변수
에 price
와 color
속성을 가진 객체
의 참조값을 저장합니다.
이 때 객체
가 저장되어 있는 메모리는 toy
라는 변수로 접근할 수 있기 때문에 닿을 수 있는
메모리가 됩니다.
toy = null;
하지만 이렇게 toy
변수에 null
을 할당하게 되면 객체
에 접근할 수 있는 경로가 사라졌기 때문에 해당 메모리는 닿을 수 없는
메모리가 되고, GC
의 대상이 됩니다.
메모리에 이중으로 참조하는 경우에는 어떻게 될까요?
let toy = {
price: '$30',
color: 'blue',
};
let stuff = toy;
toy = null;
이 경우에는 객체
에 접근할 수 있는 방법이 stuff
가 남아있기 때문에 GC
이 대상이 되지 않습니다.
JavaScript
에서 채택한 GC
알고리즘인 표시하고 쓸기(Mark-and-Sweep)
알고리즘이 어떻게 동작하는지 알아보겠습니다.
메모리 참조 트리가 위와 같이 되어있다고 가정해보겠습니다. GC
는 roots
부터 시작해서 연결되어있는 노드들을 방문합니다. 방문한 노드는 mark
를 해두면서 완전탐색 과정을 거칩니다.
탐색을 마친 뒤에 mark
가 되어있지 않은 노드들을 메모리에서 해제합니다.
이런 방식으로 동작하기 때문에 알고리즘 이름이 Mark-and-Sweep
입니다.
메모리 관리를 자동으로 해준다고 해서 GC
가 만능은 아닙니다. 이런 내용을 모르는 채로 개발을 하다보면 언젠간 메모리가 터지는 경우가 생길 수도 있고, 최적화를 하기 힘듭니다. 가능한 메모리 관리도 신경쓰며 개발하는 습관을 들이면 좋겠습니다.