C같은 저레벨 언어는 malloc() 이나 free() 같이, 수동으로 메모리를 관리해주는 원소들을 가지고 있었다. 이와 대조적으로 자바스크립트는 객체가 생성되었을 때 자동으로 메모리를 할당해주고, 더이상 사용하지 않을 때는 자동으로 메모리를 비워주었다(가비지 컬렉션). 이 자동화기능은 개발자에게 혼란을 줄 수 있다. 개발자들이 메모리를 관리할 신경쓸 필요가 없다는 잘못된 인상을 줄 수 있다.
메모리 라이프 사이클
프로그래밍 언어에 관계없이, 메모리 라이프 사이클은 항상 동일하다:
- 필요한 곳에 메모리를 할당한다
- 할당된 메모리를 사용한다 (읽기, 쓰기)
- 더 이상 필요하지 않을 때 메모리 할당을 비워준다.
2번은 모든 언어에서 명시적이다. 1번과 3번은 저레벨 언어에서는 명시적이나, 대부분의 자바스크립트같은 고레벨 언어에서는 암묵적이다.
자바스크립트에서의 할당
값 초기화
할당으로 프로그래머가 귀찮아지지 않게 하기 위해, 자바스크립트는 값이 처음 선언되었을 때 메모리를 자동으로 할당한다.
const n = 123; // number를 위한 메모리가 할당된다.
const s = 'azerty'; // string을 위한 메모리가 할당된다.
const o = {
a: 1,
b: null,
}; // 객체와 담겨있는 값들을 위한 메모리가 할당된다.
// (객체와 같이) 배열과 담겨있는 값들을 위한 메모리가 할당된다.
const a = [1, null, 'abra'];
function f(a) {
return a + 2;
} // 함수를 할당한다.(호출가능한 객체이다)
// 함수 표현식은 또한 객체에 할당할 수 있다.
someElement.addEventListener('click', function() {
someElement.style.backgroundColor = 'blue';
}, false);
함수 호출을 통한 할당
어떤 함수들은 호출의 결과로 객체를 할당시킨다.
const d = new Data(); // Date 객체를 할당한다.
const e = document.createElement('div'); // DOM 엘리먼트를 할당한다.
어떤 메소드들은 새로운 값이나 객체를 할당 시킨다.
const s = 'azerty';
const s2 = s.substr(0, 3); // s2 는 새로운 string이다.
// 왜냐하면 strings는 불변값이어서,
// 자바스크립트는 메모리를 할당하지 않기로 결정한다.
// 단지 [0, 3]의 범위를 저장한다.
const a = ['ouais ouais', 'nan nan'];
const a2 = ['generation', 'nan nan'];
const a3 = a.concat(a2);
// a와 a2의 원소를 덧붙인 4개의 원소로 이루어진 새로운 배열이다.
값을 사용하기
값을 사용하는 것은 기본적으로 할당된 메모리를 읽고 쓴다는 것을 의미한다. 변수의 값, 객체 프로퍼티, 함수로 넘겨진 아규먼트를 읽고 쓸 때 값을 사용하는 일이 일어난다.
메모리가 더 이상 필요하지 않을 때 비워주기
메모리 관리 이슈의 대부분이 이 단계에서 발생한다. 이 단계의 가장 어려운 부분은 할당된 메모리가 더 이상 필요하지 않다고 결정하는 부분이다.
저레벨 언어는 개발자가 수동으로 프로그램의 어떤 부분에 할당된 메모리가 더이상 필요하지 않다고 결정하고 비워준다.
고레벨 언어는 가비지 컬렉션(GC)로 알려진 자동화 메모리 관리를 사용한다. 가비지 컬렉터의 목적은 메모리 할당을 모니터링하고 메모리 블럭이 더이상 필요하지 않을 때 비워서 사용가능한 메모리를 되찾는 것이다. 특정한 메모리의 부분이 여전히 필요한지 아닌지 결정할 수 없을 수 있어서, 이 자동화 과정은 근사치이다.
가비지 컬렉션
위에서 다뤘듯이, 자동으로 메모리를 정리하는것의 일반적인 문제는, 어떤 메모리가 "더 이상 필요하지 않은지"는 결정할 수 없을 때이다. 결론적으로, 가비지 컬렉터는 이 문제의 해결책으로 제한을 구현했다. 이 장에서는 주요 가비지 컬렉션 알고리즘을 이해하기 위해 필요한 개념과, 각각의 한계를 설명한다.
참조
가비지 컬렉션 알고리즘의 주요 개념은 참조의 개념에 기반한다. 메모리 관리의 맥락에서, 전자가 (암묵적으로 또는 명시적으로) 다른 객체에 접근할 수 있는 경우, 객체가 다른 객체를 참조한다고 한다. 예를 들어, 자바스크립트 객체는 프로토타입에 대한 참조를 가진다(암묵적 참조). 그리고 프로퍼티 값에대해서도 참조한다(명시적 참조).
이 맥락에서, "객체"의 개념은 일반적인 자바스크립트 객체에서 확장되어 함수 범위까지 담는다( 또는 전역 렉시컬 범위 까지).
Reference-count garbage collection
가장 느슨한 가비지 컬렉션 알고리즘이다. 이 알고리즘은 어떤 객체가 다른 객체를 참조하고 있다면 필요하다고 본다. "garbage"라고 불리는 객체는 그 객체를 짚는 참조가 0개일 때 수집된다.
Example
let x = {
a: {
b: 2
}
};
// 2 개의 객체가 생성된다. 하나는 프로퍼티로 다른 하나를 참조한다.
// 다른 하나는 x 변수에 할당되는 것으로 참조된다.
// 명백히, 아무것도 가비지 컬렉트 되지 않는다.
let y = x; // 'y' 변수는 객체를 참조하는 두번째 변수이다.
x = 1; // 이제, 'x'에 원래 들어있던 객체는 'y'변수에의해 유일하게 참조된다.
let z = y.a; // 'a'프로퍼티를 객체에 참조시켰다.
// 이 객체는 이제 2개의 참조를 갖는다: 하나는 프로퍼티고,
// 다른 하나는 'z' 변수이다.
y = 'mozilla'; // 'x'에 원래 들어있던 객체는 이제 0개의 참조를 갖는다.
// 이제 가비지 컬렉트 될 수 있다.
// 하지만, 'a'프로퍼티는 여전히 'z'변수에 의해 참조된다.
// 'a' 프로퍼티는 가비지 컬렉트 되지 않는다.
z = null; // 원래 'x'에 있는 객체의 'a'프로퍼티는 이제 0개의 참조를 갖는다.
// 가비지 컬렉트 될 것이다.
한계: 순환 참조
이 알고리즘은 순환참조일 때 한계가 있다. 다음 예제에서, 두개의 객체가 서로를 참조하는 프로퍼티를 가진체로 생성되어,순환관계를 만든다. 함수 호출이 끝나면 이 객체들은 다시 쓰일일이 없다. 이 경우, 객체들은 더 이상 필요하지 않고, 그들이 할당된 메모리는 반환되어야 한다. 하지만, reference-counting 알고리즘은 이 객체들을 반환가능하다고 여기지 않는다. 두 객체 각각에 서로를 가르키는 적어도 하나의 참조가 있기 때문에, 가비지 컬렉션이 마크하지 않게 된다. 순환 참조가 메모리 누수를 일으키는 주요 원인이다.
function f() {
const x = {};
const y = {};
x.a = y; // x references y
y.a = x; // y references x
return 'azerty';
}
f();
인터넷 익스플로어 6와 7은 순환 참조로 메모리 누수를 일으키는 reference-counting 가비지 컬렉터를 사용한다고 알려져있다. 더이상 현대적인 엔진은 reference-counting 가비지 컬렉터를 사용하지 않는다.
Mark-and-sweep algorithm
이 알고리즘은 "더 이상 필요하지 않은 객체"에서 "접근할 수 없는 객체"로 정의를 좁혔다.
이 알고리즘은 루트라고 불리는 객체 집합을 가정한다. 자바스크립트에서, 루트는 전역 객체다. 정기적으로, 가비지 컬렉터는 루트부터 시작해서 이 루트에서 참조되는 모든 객체를 찾는다. 루트에서 시작된 가비지컬렉터는 모든 접근가능한 객체를 찾은 후에, 접근할 수 없는 객체들을 수집한다.
이 알고리즘은 이전에 0개의 참조를 갖는 객체에는 접근할 수 없다는 것을 효과적으로 개선한 것이다. 순환 참조에서 보았던 것은 더 이상 성립하지 않는다.
2012년 기준으로, 모든 최신 브라우저들은 mark-and-sweep 가비지 컬렉터를 제공한다. 자바스크립트 가비지 컬렉션 분야에서(생성/증가/동시/병력 가비지 컬렉션) 은 지난 몇년간 이 알고리즘의 구현 개선이지만, 이 알고리즘 자제의 개선이나, "더 이상 필요하지 않을 때"의 정의 축소는 아니었다.
순환 참조는 더이상 문제가 아니다.
위의 순환참조 예에서, 함수가 호출하고 'azerty'를 반환한 후에, 두 객체는 전역 객체가 접근가능한 어떤 자원에서도 더이상 참조되지 않는다. 따라서, 가비지 컬렉터에의해 접근불가능하다고 판단되고, 메모리를 반환한다.
한계: 수동으로 메모리를 반환하는 일
언제 어떤 메모리를 반환할지 수동으로 결정하는게 편리한 경우가 있다. 객체의 메모리를 반환하기 위해, 우리는 명시적으로 접근 불가능하게 만들어야 한다.
2019년 기준으로, 자바스크립트에서 가비지 컬렉션을 명시적이나 코드로서 실행시키는건 불가능하다.
Node.js
node.js는 브라우저 환경에서 실행되는 자바스크립트에서 불가능했던, 메모리 이슈를 설정하고 디버깅 하기 위한 추가적인 옵션과 도구를 제공한다.
V8 Engine Flags
플래그를 통해 증가되는 힙 메모리의 최대치를 정할 수 있다:
node --max-old-space-size=6000 index.js
크롬 디버거와 플래그를 사용해서 메모리 이슈를 디버깅하기위한 가비지 컬렉터를 노출할 수 있다.
node --expose-gc --inspect index.js
'front > js' 카테고리의 다른 글
[javascript] 반복하고 싶을 때 사용하는 함수 (0) | 2022.08.31 |
---|---|
[js] 배열의 참조를 유지하면서 특정 값을 모두 제거하는 방법. (0) | 2022.08.09 |
[js] Arrow function expressions (1) | 2022.08.01 |
[javascript] falsy (0) | 2022.07.26 |
[에러]TypeScript language service died unexpectedly 5 times in the last 5 minutes (0) | 2022.07.23 |
댓글