본문 바로가기
웹 프론트엔드/Javascript

Javascript 메모리 관리 (가비지 컬렉션) 알고리즘 알아보기

by canoe726 2020. 10. 19.
728x90

자바스크립트의 메모리 관리 및 가비지 컬렉션과 관련해 공부한 내용에 대해서 포스팅을 작성해 보았습니다.

 

 

1. 가비지 컬렉션이란? (GC)

 

자바스크립트는 객체가 생성되었을 때 자동으로 메모리를 할당하고 쓸모 없어졌을 때 자동으로 해제한다.

 

그렇다면 메모리는 언제 생겨나고 없어질까? 메모리의 생존주기는 아래와 같다.

 

 

1-1. 메모리 생존주기

 

1) 필요할때 할당한다.

 

2) 사용한다. (읽기, 쓰기) 

 

3) 필요없을 때 해제한다.

 

 

1)과 3)은 가비지 컬렉션을 사용하는 Javascript 에서는 암묵적으로 작동하며, C 언어와 같은 저수준 언어에서는 아래와 같이 명시를 해주어야 한다.

 

#include<stdlib.h>

int main() {
    int pNum = malloc(sizeof(int)); // 4 byte, int 형 데이터 할당
    free(pNum);                     // 동적으로 할당한 메모리 해제
}

 

저수준 언어인 C 언어에서는 malloc, free 키워드를 통해 메모리를 할당하고 해제할 수 있지만, Javascript 에서는 언제 어떻게 메모리가 할당될까?

 

 

1-2. Javascript 에서 메모리가 할당되는 경우

 

1) 값 초기화 : 값을 선언할 때 메모리 할당

 

var number = 8;

var name = 'kim';

var obj = {
    a: 1,
    b: 'young'
};

 

 

2) 함수 호출 : 함수 호출 결과 메모리 할당이 일어나기도 한다. (값 또는 오브젝트)

 

var arr1 = ['hello'];
var arr2 = ['world'];
var arr3 = arr1.concat(arr2);

console.log(arr3);
// ["hello", "world"]

 

 

* 값 사용 : 할당된 메모리를 읽고 쓰는 것 = 변수나 객체를 읽고 쓸 때 = 함수 호출 시 함수에 인수를 넘길 때

 

 

2. Javascript 가비지 컬렉션 알고리즘 2가지

 

할당된 메모리가 더 이상 필요없을 때 메모리가 해제된다. 그럼 할당된 메모리가 더 이상 사용되지 않을 때는 어떻게 알아낼까?

 

 

1. 참조세기 (Reference-counting) 알고리즘

 

참조세기 (Reference-counting) 알고리즘은 "더 이상 필요없는 오브젝트를"를 "어떤 다른 오브젝트도 참조하지 않는 오브젝트"라고 정의하는 알고리즘이며, 참조하지 않는 오브젝트를 "가비지"라고 부른다.
(참조하지 않을 때 가비지로 수집 가능)

 

 

 

* 참조 이해하기

 

A라는 메모리를 통해 B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다고 볼 수 있다."

 

(명시적이든 암시적이든)

 

 

ex1) C 언어의 포인터와 비슷한 개념

 

ex2) 모든 Javascript 오브젝트는 prototype 를 암시적으로 참조하고 오브젝트의 속성을 명시적으로 참조한다.

 

ex3) 코드를 통한 이해

 

const a = [1, 'hello', 99];
let b = a;

b.push(123);

console.log(a);
console.log(b);

// [1, "hello", 99, 123]

 

b 라는 변수가 a 배열을 참조하고 있으며, b 는 a 의 메모리에 접근가능하다.

 

참조 관계이기 때문에 a 또는 b 의 값을 바꾸게 되면, 동시에 a 와 b 의 값이 변경된다. 

 

 

* 참조세기 알고리즘 예제

 

var obj1 = {
    obj2: {
        val: 2
    }
};

var p1 = obj1;
obj1 = 0;

var p2 = obj1.obj2;

// 이때 가비지 컬렉션이 일어날까? : No
// p1 은 obj1 을 가리키고 있다.
// p2 은 obj2 를 가리키고 있다.
// obj1 의 값이 바뀌었지만 p1 과 p2 가 obj1 객체의 메모리를 가리키고 있어 메모리가 해제되지 않는다.

p1 = 'ok';

p2 = null;

// 이때 가비지 컬렉션이 일어날까? : Yes
// p1, p2 의 값 모두 obj1, obj2 객체를 가리키지 않게된다.
// 메모리에 할당된 obj1, obj2, val 객체를 어떠한 변수도 가리키지 않게된다.
// 할당되지 않은 값들은 가비지로 인식 된다.

 

 

그림을 통해서 참조 관계가 어떻게 되는지 파악해 볼 수 있다.

 

 

가비지 컬렉션이 일어나지 않는 이유 : 참조되고 있기 때문

 

 

 

 

* 한계 : 순환참조

 

두 객체가 서로 참조하는 속성으로 순환 구조가 생성되는 경우 발생

 

function recursion(){
  var obj1 = {};
  var obj2 = {};
  obj1.a = obj2; // obj1는 obj2를 참조한다.
  obj2.a = obj1; // obj2는 obj1를 참조한다.

  return "ref each other";
}

recursion();

 

원래라면 obj1, obj2 는 recursion() 함수의 지역변수로 할당되어, recursion() 함수 실행 후 메모리가 해제 되어야 하지만 실제 메모리 상에서는 두 객체가 서로를 참조하고 있기 때문에 메모리가 해제되지 않게 된다.

 

결국 recursion() 함수가 호출 될 때마다 obj1, obj2 객체 만큼의 메모리가 할당된 뒤 해제되지 않고 남게 되어 메모리가 낭비 될 것이다.

 

 

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

 

표시하고 쓸기 (Mark-and-sweep) 알고리즘은 "더 이상 필요없는 오브젝트를"를 "닿을 수 없는 오브젝트"라고 정의하는 알고리즘이다.

 

 

주기적으로 가비지 콜렉터는 roots 로부터 시작하여 roots 가 참조하는 오브젝트들, 참조되는 오브젝트가 참조하는 오브젝트 ... 로 진행하면서 닿을 수 있는 오브젝트를 표시한다.

 

그리고 닿을 수 없는 오브젝트들에 대해 가비지 콜렉션을 수행한다.

 

=> "닿을 수 없는 오브젝트"라는 개념을 적용하게 되면 순환참조로 인한 메모리 누수 문제는 생기지 않는다.

 

 

* 서로 같은 의미 : 닿을 수 있는 오브젝트 = 도달 가능한 값 (reachable) 

 

 


- "닿을 수 있는 오브젝트"가 되는 기준은?

 

1. 원래 닿을 수 있는 오브젝트 (root = 루트)

 

ex 현재 함수의 지역 변수와 매개 변수, 전역 변수...

 

 

2. root 가 참조하는 값이나 루트에서 참조할 수 있는 값

 

 

표시하고 쓸기 알고리즘 예제

 

1) 하나의 객체를 두 번 참조하는 경우

 

let obj = {name: 'kim'};
let pointer = obj;
obj = null;

 

위의 소스코드를 그림으로 표현하면 아래와 같다.

 

 

roots -> pointer -> obj 로 접근이 가능하기 때문에 해제되지 않음

 

 

2) 객체간 참조 상황

 

객체간 서로 참조를 하고 있어도 roots 에서 도달 불가능한 경우 메모리가 해제된다.

 

 

roots 에서 도달이 불가능 하면 메모리가 해제된다.

 

 

* 한계 : 수동 메모리 해제

 

어떤 메모리를 언제 해제할지에 대해 수동으로 결정하려면 수동으로 "닿을 수 없는 객체"를 만들 수 있어야 하지만 표시하고 쓸기 알고리즘에서는 그 기능을 지원하지 않아 수동으로 메모리 해제를 할 수 없습니다.

 

 

- 참고한 사이트

 

1. MDN 사이트 

 

자바스크립트의 메모리 관리 - JavaScript | MDN

C 언어같은 저수준 언어에서는 메모리 관리를 위해 malloc() 과 free()를 사용합니다. 반면, 자바스크립트는 객체가 생성되었을 때 자동으로 메모리를 할당하고 더 이상 필요하지 않을 때 자동으로

developer.mozilla.org

 

2. Javascript Info

 

가비지 컬렉션

 

ko.javascript.info

 

 

728x90

댓글