C 언어같은 저수준 언어에서는 메모리 관리를 위해 malloc() 과 free()를 사용해 메모리를 수동으로 해제 한다.
반면, 자바스크립트는 객체가 생성되었을 때 자동으로 메모리를 할당하고 더 이상 필요하지 않을 때 자동으로 해제한다. 이를 가비지 컬렉션이라고 한다.
자바스크립트 엔진에 들어있는 가비지 콜렉터는 메모리 할당을 모니터링하고 할당된 메모리의 영역이 더 이상 필요하지 않은 시점을 확인하여 메모리를 회수한다.
가비지 컬렉션을 이해하기 위해서 일딴 메모리 생존주기 부터 알아야 된다.
1) 값초기화: 자바스크립트는 값을 초기화 할 때 자동으로 메모리를 할당한다.
var n = 123; // 정수를 담기 위한 메모리 할당
var s = 'azerty'; // 문자열을 담기 위한 메모리 할당
// 객체와 객체에 포함된 값들을 담기 위한 메모리 할당
var o = {
a: 1,
b: null
};
// 배열과 포함된 값들을 담기 위한 메모리 할당
var a = [1, null, 'abra'];
// 함수를 담기 위한 메모리 할당
function f(a) {
return a + 2;
}
// 함수 표현식 또한 객체를 담기 위한 메모리를 할당
someElement.addEventListener('click', function () {
someElement.style.backgroundColor = 'blue';
}, false);
2) 함수 호출을 통한 메모리 할당
var d = new Date(); // Date 객체 할당
var e = document.createElement('div'); // DOM element 할당
var s = 'azerty';
var s2 = s.substr(0, 3);
// s2는 새로운 문자열입니다. //문자열은 불변값(immutable)이기 때문에 자바스크립트는 메모리를 새로 할당하지 않고, [0, 3]의 범위를 저장합니다.
var a = ['ouais ouais', 'nan nan'];
var a2 = ['generation', 'nan nan'];
var a3 = a.concat(a2); // 4개의 원소를 가진 새로운 배열
값을 사용한다는 의미는 "할당된 메모리"를 "쓰거나 읽는다는 것"을 의미한다.
"변수나 객체 속성값을 읽고 쓸 때", "함수 호출 시 함수에 인자를 넘길 때" "값을 사용하는 것"을 의미한다.
할당된 메모리를 해제하려면 할당된 메모리가 더 이상 필요하지 않은 시기를 결정해야 하는데,
이를 잘못하면 메모리 누수가 발생하게 된다. 그러므로 메모리 누수 문제는 할당한 메모리를 적절히 해제하지 못해서 발생한다고 볼 수 있다.
C, C++과 같은 로우 레벨 언어에서는 개발자가 메모리 해제 시기를 직접 결정해야 한다. 프로그램에서 할당된 메모리가 더 이상 필요하지 않을 때, 직접 메모리 할당을 해제해야 한다.
자바스크립트와 같은 몇몇 하이 레벨 언어에서는 "가비지 컬렉션(GC)"이라는 "자동 메모리 관리형식"을 활용한다.
가비지 콜렉터의 목적은 메모리 할당을 모니터링하고 할당된 메모리의 영역이 더 이상 필요하지 않은 시점을 확인하여 회수하는 것이다.
가비지 콜렉션 알고리즘의 핵심 개념은 참조이다.
A라는 메모리를 통해 (명시적이든 암시적이든) B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다."라고 이야기한다.
예를 들어 자바스크립트에서 모든 객체는 prototype 객체를 암시적으로 참조하고, 그 객체의 속성을 명시적으로 참조한다
이 알고리즘은 "더 이상 필요 없는 객체"를 "어떤 다른 객체도 참조하지 않는 객체"라고 정의한다.
특정 객체를 참조하는 객체가 하나도 없다면, 그 객체에 대해 가비지 컬렉션을 수행한다.
var o = {
a: {
b: 2
}
};
여기서 각 객체는 모두 참조되고 있기 때문에 가비지 컬렉션이 수행될 객체는 없다.
let x = {
a: {
b: 2
}
}
할당된 모든 메모리가 참조당하고 있는 상태로, 가비지가 없는 상태
전역 변수는 가비지 컬렉션의 대상이 아니므로 x에 대한 카운팅을 제외
let y = x
x = 1
변수 y에 변수 x를 대입하여 객체의 메모리 주소를 연결
변수 x에는 1을 할당하여 객체와의 연결을 끊은 상태
할당된 모든 메모리가 참조당하고 있는 상태로, 가비지가 없는 상태
let z = y.a.b
y = 'bumsu'
변수 z에 y.a.b를 대입하면 객체 b의 메모리 주소가 연결됨
변수 y에는 문자열을 할당하여 객체 a와의 연결을 끊은 상태
*객체 a를 참조하는 변수가 하나도 없기 때문에 가비지로 인식하고 메모리 공간을 해제
z = null
변수 z에 null을 대입하여 객체 b와의 연결을 끊은 상태
*객체 b가 가비지로 인식되어 메모리 공간이 해제되고, 그에 따라 숫자 2도 메모리 해제
이 알고리즘은 두 객체가 서로를 참조하면 문제가 발생한다. 두 객체 모두 더 이상 사용하지 않더라도 가비지 컬렉션을 수행할 수 없게 된다.
function f() {
var x = {};
var y = {};
x.a = y;
y.a = x;
return 'azerty';
}
f();
위 코드에서 f 함수가 종료되고 나면, x, y에 저장한 객체는 사용되지 않으므로 가비지 컬렉션이 수행되어야 한다.
하지만 Reference-counting(참조-세기) 알고리즘에서는 두 객체 모두 참조를 가지고 있기 때문에 가비지 콜렉션이 수행되지 않는다.
이 알고리즘에서는 "더 이상 필요 없는 객체"를 "닿을 수 없는 객체"로 정의한다. 이름에서 알 수 있듯이 무엇인 가에 표시(Mark)를 하고, 정리하는(Sweep) 알고리즘이다.
이 알고리즘은 roots라는 객체의 집합을 가지고 있다.(자바스크립트에서는 전역 변수들을 의미)
주기적으로 가비지 콜렉터는 roots로부터 시작하여 roots가 참조하는 객체들, roots가 참조하는 객체가 참조하는 객체들을 접근할 수 있는 객체라고 표시한다. 그 후, 접근할 수 없는 객체에 대해 가비지 컬렉션을 수행한다.
이 알고리즘은 "참조되지 않는 객체"는 모두 "접근할 수 없는 객체"이지만 역은 성립하지 않기 때문에 참조-세기 알고리즘보다 효율적이라고 할 수 있다.
function f() {
var x = {};
var y = {};
x.a = y;
y.a = x;
return 'azerty';
}
f();
위 코드에서 함수 f가 리턴되고 나면, 전역 변수들에서 x, y에 담긴 객체들에 접근할 수 있는 방법이 없다. 따라서 두 객체에 대해 가비지 컬렉션이 수행될 수 있습니다