ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JavaScript] 메모리 관리, GC(가비지 컬렉션)
    JavaScript 2022. 6. 20. 16:30
    728x90

    메모리 생존주기

    1. 필요할 때 할당합니다.
    2. 할당된 메모리를 사용합니다. (읽기, 쓰기)
    3. 더 이상 필요하지 않으면 해제합니다.

    2번은 모든 언어에서 명시적으로 사용합니다. 그러나 1번과 3번은 저수준 언어에서는 명시적이며, JavaScript와 같은 대부분의 고수준 언어에서는 암묵적으로 작동합니다.

    JavaScript에서 메모리 할당

    프로그래머를 할당 문제로 괴롭히지 않기 위해서, 자바스크립트는 값을 선언할 때 자동으로 메모리를 할당합니다.

    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);
    

    함수 호출을 통한 할당

    함수 호출의 결과 메모리 할당이 일어나기도 합니다.

    var d = new Date(); // Date 개체를 위해 메모리를 할당
    
    var e = document.createElement('div'); // DOM 엘리먼트를 위해 메모리를 할당
    

    메소드가 새로운 값이나 오브젝트를 할당하기도 합니다.

    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);
    // a 와 a2 를 이어붙여, 4개의 원소를 가진 새로운 배열
    
    • immutable : 변경 불가능

    값 사용

    값 사용이란 기본적으로는 할당된 메모리를 읽고 쓰는 것을 의미합니다. 변수나 객체 속성의 값을 읽고 쓰거나 함수 호출 시 함수에 인수를 전달하여 수행 할 수 있습니다.

    할당된 메모리가 더 이상 필요없을 때 해제하기

    이 단계에서 대부분의 문제가 발생합니다. “할당된 메모리가 더 이상 필요없을 때”를 알아내기 힘들기 때문입니다.

    저수준 언어는 메모리가 필요없어질 때를 개발자가 직접 결정하고 해제하는 방식을 사용합니다.

    JavaScript와 같은 고수준 언어들은 “가비지 콜렉션(GC)”이라는 자동 메모리 관리 방법을 사용합니다. 가비지 콜렉터의 목적은 메모리 할당을 추적하고 할당된 메모리 블록을 더 이상 필요하지 않게 되었는지를 판단하여 회수하는 것입니다.

    이러한 자동 메모리 관리 프로세스는 궁극의 방법은 아닙니다. 왜냐하면 어떤 메모리가 여전히 필요한지 아닌지를 판단하는 것은 비결정적 문제이기 때문입니다.

    가비지 콜렉션

    이번 파트에서는 주요 가비지 컬렉션 알고리즘들과 그 한계를 이해하는데 필요한 개념을 알아보겠습니다.

    참조(Referece)

    가비지 콜렉션 알고리즘의 핵심 개념은 참조입니다. A라는 메모리를 통해 (명시적이든 암시적이든) B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다."라고 이야기합니다. 예를 들어 자바스크립트에서 모든 객체는 prototype 객체를 암시적으로 참조하고, 그 객체의 속성을 명시적으로 참조합니다.

    참조-세기(Reference-counting) 가비지 컬렉션

    이 알고리즘은 "더 이상 필요 없는 객체"를 "어떤 다른 객체도 참조하지 않는 객체"라고 정의합니다. 특정 객체를 참조하는 객체가 하나도 없다면, 그 객체에 대해 가비지 컬렉션을 수행합니다.

    var x = {
      a: {
        b: 2
      }
    };
    // 2개의 오브젝트가 생성되었습니다. 하나의 오브젝트는 다른 오브젝트의 속성으로 참조됩니다.
    // 나머지 하나는 'x' 변수에 할당되었습니다.
    // 명백하게 가비지 콜렉션 수행될 메모리는 하나도 없습니다.
    
    var y = x;      // 'y' 변수는 위의 오브젝트를 참조하는 두 번째 변수입니다.
    
    x = 1;          // 이제 'y' 변수가 위의 오브젝트를 참조하는 유일한 변수가 되었습니다.
    
    var z = y.a;    // 위의 오브젝트의 'a' 속성을 참조했습니다.
                    // 이제 'y.a'는 두 개의 참조를 가집니다.
                    // 'y'가 속성으로 참조하고 'z'라는 변수가 참조합니다.
    
    y = "mozilla";  // 이제 맨 처음 'y' 변수가 참조했던 오브젝트를 참조하는 오브젝트는 없습니다.
                    // (역자: 참조하는 유일한 변수였던 y에 다른 값을 대입했습니다)
                    // 이제 오브젝트에 가비지 콜렉션이 수행될 수 있을까요?
                    // 아닙니다. 오브젝트의 'a' 속성이 여전히 'z' 변수에 의해 참조되므로
                    // 메모리를 해제할 수 없습니다.
    
    z = null;       // 'z' 변수에 다른 값을 할당했습니다.
                    // 이제 맨 처음 'x' 변수가 참조했던 오브젝트를 참조하는
                    // 다른 변수는 없으므로 가비지 콜렉션이 수행됩니다.
    

    한계 : 순환 참조

    참조-세기 가비지 컬렉션은 순환 참조를 다루는 일에 한계가 있습니다.

    function f() {
      var x = {};
      var y = {};
      x.a = y;         // x는 y를 참조합니다.
      y.a = x;         // y는 x를 참조합니다.
    
      return "azerty";
    }
    
    f();
    

    위 예제는 f 함수가 종료되고 나면 x, y 객체는 더 이상 사용되지 않으므로 가비지 컬렉션이 수행돼야 하지만 Reference-counting(참조-세기) 알고리즘에서는 두 객체가 참조를 갖고 있으므로 가비지 컬렉션이 수행되지 않는다는 한계를 가지고 있습니다.

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

    이 알고리즘에서는 "더 이상 필요 없는 객체"를 "닿을 수 없는 객체"로 정의합니다.

    이름에서 알 수 있듯이 무엇인 가에 표시(Mark)를 하고, 정리하는(Sweep) 알고리즘입니다.

    이 알고리즘은 roots라는 객체의 집합을 가지고 있습니다.(자바스크립트에서는 전역 변수들을 의미합니다.)

    주기적으로 가비지 콜렉터는 roots로부터 시작하여 roots가 참조하는 객체들, roots가 참조하는 객체가 참조하는 객체들을 접근할 수 있는 객체라고 표시합니다. 그 후, 접근할 수 없는 객체에 대해 가비지 컬렉션을 수행합니다.

    이 알고리즘은 "참조되지 않는 객체"는 모두 "접근할 수 없는 객체"이지만 은 성립하지 않기 때문에 참조-세기 알고리즘보다 효율적이라고 할 수 있습니다.

    2012년 기준, 모든 최신 브라우저들이 가비지 컬렉션에서 Mark-and-sweep 알고리즘을 사용한다고 합니다. 그 후로도 연구되고 있는 자바스크립트 가비지 콜렉션 알고리즘들은 대부분 이 알고리즘을 연구하여 개선한 것이라고 합니다. 그리고 개선된 알고리즘들도 여전히 "더 이상 필요 없는 객체"를 "닿을 수 없는 객체"로 정의하고 있습니다.

    순환 참조 문제를 어떻게 해결했을까?

    function f() {
      var x = {}; 
      var y = {};
    
      x.a = y;
      y.a = x;
      return 'azerty';
    }
    
    f();
    

    위 코드에서 함수 f가 리턴되고 나면, 전역 변수들에서 x, y에 담긴 객체들에 접근할 수 있는 방법이 없습니다. 따라서 두 객체에 대해 가비지 컬렉션이 수행될 수 있습니다.

    동작 방식

    이 알고리즘은 아래 두 단계로 작동합니다.

    1. Mark
      • 객체가 생성될 때마다 mark bit가 0 (false)로 설정됩니다.Mark 단계에서 모든 접근 가능한 객체의 mark bit가 1 (true)로 설정됩니다.
    2. SweepMark
      • 단계 후에 mark bit가 여전히 0 (false)로 설정된 객체들은 도달할 수 없는 객체이므로 가비지 콜렉터가 수집해 메모리에서 해제됩니다.

    한계: 수동 메모리 해제

    어떤 메모리를 언제 해제할지에 대해 수동으로 결정하는 것이 편리할 때가 있습니다. 그리고 수동으로 객체의 메모리를 해제하려면, 객체 메모리에 도달할 수 없도록 명시하는 기능이 있어야 합니다.

    2019년 현재의 JavaScript에서는 명시적으로 또는 프로그래밍 방식으로 가비지 컬렉션을 작동할 수 없습니다.

     

    참고

    https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management

    https://eblee-repo.tistory.com/52

    728x90

    'JavaScript' 카테고리의 다른 글

    연산자(JavaScript)  (0) 2022.06.27
    데이터 타입(js)  (0) 2022.06.22
    [JavaScript]비동기 프로그래밍  (0) 2022.06.17
    표현식과 문(javascript)  (0) 2022.06.16
    변수(js)  (0) 2022.06.15

    댓글

Designed by Tistory.