[Javascript] 메모리 관리(Garbage Collector)

메모리 생존 주기

프로그래밍 언어는 그 종류와 상관없이 비슷한 메모리 생존 주기를 가진다.
  1. 필요할 때 할당한다.
  2. 해당 메모리를 사용한다(읽기, 쓰기).
  3. 메모리 사용이 불필요해질 경우 해제한다.
위의 순서에서 1, 2번은 명확하게 정의 할 수 있다. 
하지만 3번의 경우 언어에 따라 그 정의가 달라진다. 

저급 언어에서는 명시적으로 메모리 할당을 해제하지만, Javascript와 같은 고급 언어는 명시적 해제가 이루어지지 않는다. 때문에 가비지 콜렉터(Garbage Collector)의 도움을 받는다.

메모리 생존 주기를 한 단계씩 살펴보자.

출처: Valtteri Mäki


1. 메모리 할당

값 초기화

자바스크립트에서는 변수를 선언하는 순간 메모리를 자동으로 할당한다.
const n = 123; //정수형 변수 메모리 할당
const s = 'string'; //문자열 변수 메모리 할당

const o = {
    a: 'abc',
    b: 123
}; //객체 및 객체의 구성 요소에 대한 메모리 할당

const a = [1, 2, 3]; //배열 및 구성 요소에 대한 메모리 할당

const u; //javascript에서는 초기값을 부여하지 않았을 경우 undefined로 값을 배정하고 메모리를 할당한다.    

함수 호출을 통한 할당

함수를 call 하는 순간에도 메모리 할당이 일어난다.
const d = new Date(); // Date 객체 메모리 할당
const e = document.createElement('div'); // DOM 엘리먼트를 메모리 할당

2. 메모리(값) 사용

메모리 사용이란 할당된 메모리를 읽기/쓰기 하는 과정을 의미한다. 
변수 또는 객체의 속성값을 읽고 쓸때 발생하며, 함수 호출 및 인수를 넘길 때도 일어난다.

3. 메모리 해제

앞의 과정은 프로그래머에 의해 명확하게 통제되지만, 메모리 해제는 그렇지 않다. 그 이유는 '해당 메모리가 더이상 필요하지 않을 때'를 정확하게 알아내는 것이 힘들기 때문이다. 

Javascript를 포함한 고급언어 인터프리터는 가비지 콜렉터를 사용한다. 이는 메모리의 할당을 추적하고 메모리가 더이상 필요없어졌을 때 해제하는 작업을 한다. 이 작업은 100% 신뢰되지는 않는다. 그 이유는 어떤 메모리가 필요없는지 알아내는 것은 알고리즘으로 품 수 없는 비결정적인 문제이기 때문이다. 가비지 콜렉터는 항상 필요없어진 메모리만을 해제하지만 모든 필요없어진 메모리를 해제하는건 아니다.


가비지 콜렉션

가비지 콜렉션이 사용하는 알고리즘과 그 한계점을 살펴보자.
가비지 콜렉션 알고리즘의 핵심 개념은 참조이다. A라는 메모리를 통해 B라는 메모리에 접근 할 수 있다며 "B는 A에 참조된다"라고 한다. 예를 들어 Javascript객체는 prototype을 암시적으로 참조하고 그 객체의 속성을 명시적으로 참조한다.

참조-카운팅(Reference-counting) 알고리즘

참조-카운팅 알고리즘은 가장 무난한 알고리즘이다. 이 알고리즘은 더이상 필요없는 객체를 다음과 같이 정의한다. 

어떤 다른 오브젝트도 참조하지 않는 오브젝트. 

어떤 오브젝트를 참조하는 다른 객체가 하나도 없으면 그 객체에 대해 가비지 콜렉션을 수행한다.

ex)
let o = {
    a: {
        b: 1
    }
};
//o 객체와 o.a 객체가 생성된다.

let o2 = o; //o2 변수는 o 객체의 메모리를 를 '참조'한다.
o = 1;//이제 기존 o 변수의 메모리를 참조하는 변수는 o2 뿐이다.

const o3 = o2.a; // o2.a는 o2와 o3 2개의 변수가 참조한다.

o2 = "string" //이제 처음 o변수가 참조했던 메모리를 참조하는 변수는 없다. 
//하지만 o3가 o.a의 메모리를 참조하기 때문에 o의 메모리에 가비지 콜렉션이 수행되지는 않는다.

oa3 = null; //이제 모든 메모리 참조가 사라졌기 때문에 o 메모리의 가비지 콜렉션이 실행된다.

※ 이 알고리즘은 두 오브젝트가 서로를 참조하면 필요가 없어지더라도 가비지 콜렉션이 수행되지 않는다.


표시하고-쓸기(Mark-and-sweep) 알고리즘

이 알고리즘은 더이상 필요없는 오브젝트를 닿을 수 없는 오브젝트로 정의한다.

이 알고리즘은 roots 라는 오브젝트의 집합을 가지고 있다(자바스크립트에서는 전역 변수들을 의미한다). 주기적으로 가비지 콜렉터는 roots로 부터 시작하여 roots가 참조하는 오브젝트들, roots가 참조하는 오브젝트가 참조하는 오브젝트들... 을 닿을 수 있는 오브젝트라고 표시한다. 그리고 닿을 수 있는 오브젝트가 아닌 닿을 수 없는 오브젝트에 대해 가비지 콜렉션을 수행한다.

이 알고리즘은 위에서 설명한 참조-세기 알고리즘보다 효율적이다. 왜냐하면 "참조되지 않는 오브젝트"는 모두 "닿을 수 없는 오브젝트" 이지만 역은 성립하지 않기 때문이다. 위에서 반례인 순환 참조하는 오브젝트들을 설명했다.

2012년 기준으로 모든 최신 브라우저들은 가비지 콜렉션에서 표시하고-쓸기 알고리즘을 사용한다. 지난 몇 년간 연구된 자바스크립트 가비지 콜렉션 알고리즘의 개선들은 모두 이 알고리즘에 대한 것이다. 개선된 알고리즘도 여전히 "더 이상 필요없는 오브젝트"를 "닿을 수 없는 오브젝트"로 정의하고 있다.

순환 참조는 이제 문제가 되지 않는다.

첫 번째 예제에서 함수가 리턴되고 나서 두 오브젝트는 닿을 수 없다. 따라서 가비지 콜렉션이 일어난다.

두 번째 예제에서도 마찬가지다. div 변수와 이벤트 핸들러가 roots로 부터 닿을 수 없어지면 순환 참조가 일어났음에도 불구하고 가비지 콜렉션이 일어난다. (역자2: div 선언을 블럭안에다 넣어야 된다.(테스트는 못 해봤다.))

한계: 오브젝트들은 명시적으로 닿을 수 없어져야 한다.

이 한계가 지적되었지만 실제로는 사람들은 이 문제를 비롯한 가비지 콜렉션에 별 관심이 없다.


참고 문서: https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management


















댓글

이 블로그의 인기 게시물

[IIS] IIS 7.5 HTTP 오류 401.3 - Unauthorized 해결방법

[IIS] OraOLEDB.Oracle.1 설치 방법

[ASP] Server.CreateObject를 호출하지 못했습니다. 이 개체에 액세스할 수 없습니다.