Skip to content

Latest commit

 

History

History
executable file
·
197 lines (129 loc) · 9.85 KB

Garbage_Collector.md

File metadata and controls

executable file
·
197 lines (129 loc) · 9.85 KB
title date author category
Garbage Collector
2020-07-01 17:00:00 -0700
snowjang24
Javascript

Garbage Collector

학습 목표

  • 메모리 생존 주기에 대해 이해하기
  • Garbage Collection 알고리즘의 종류와 방식에 대해 이해하기

들어가며

C와 같은 언어에서는 보통 malloc()이나 free()와 같은 메서드를 통해 개발자가 직접 메모리를 관리한다. 이와 달리 자바스크립트는 Garbage Collection이라는 과정을 통해 자동으로 메모리를 관리한다. 이 때문에 개발자는 메모리에 관여할 필요가 없다고 생각하여, 메모리에 대한 고려는 하지 않고 프로그래밍을 하게 된다. 이는 잘못된 생각이며 이번 글을 통해 왜 그런지와 어떻게 관리해야 하는 가에 대해 함께 알아보자.

메모리 생존 주기

일반적으로 메모리 생존 주기는 대부분 아래와 같다.

  1. Allocate Memory(메모리 할당)
  2. Use Memory(메모리 사용)
  3. Release Memory(메모리 해제)

이 과정 중 2는 모두 동일하고, C와 같이 Low level 언어의 경우 1, 3을 직접 지정하여 작동하지만, 자바스크립트와 같은 대부분의 Highevel언어에서는 암묵적으로 작동한다.

1. JS에서의 메모리 할당

값 초기화

자바스크립트는 값을 선언할 때 메모리 할당을 스스로 수행한다. 아래의 예시와 같이 선언을 하는 시점에 메모리 할당이 자동으로 이뤄진다.

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개의 원소를 가진 새로운 배열

2. JS에서의 메모리 사용

메모리 사용은 말 그대로 할당된 메모리에 읽고 쓰는 것을 의미한다. 객체의 속성이나 변수의 값을 읽거나 쓸 때 혹은 함수에 인자를 넘겨줄 때에도 발생한다.

3. JS에서의 메모리 해제

앞에서는 우리가 흔히 아는 내용이고 어떻게 크게 문제가 될게 없다. 진짜 문제는 메모리 가 더 이상 필요하지 않을 때 메모리 해제를 제대로 못하기 때문에 발생한다.

메모리 해제를 언제 해야 하는지 아는 것은 매우 어렵다. 일반적으로 High level언어에서는 가비지 컬렉터라는 소프트웨어가 기본으로 내장되어 메모리 할당을 추적하고 필요하지 않는 경우 메모리 해제를 자동으로 시행한다.

이때, 이러한 과정은 가비지 콜렉터의 추정에 의해 일어난다. 어떤 할당된 메모리의 값이 언제 쓰일지 모르기 때문에 제한적인 해결책을 가지고 메모리 해제를 진행하게 된다.

이러한 제한적 해결책에 대해 알아보도록 하자.

Garbage Collection 알고리즘

앞에서 가비지 컬렉터는 제한적인 해결책을 제시한다고 했다. 잘 알려진 가비지 컬렉션 방법은 아래와 같이 두 가지 방법이 있다.

  1. 참조 횟수(Reference Counting) 알고리즘
  2. 표시하고 쓸기(Mark and weep) 알고리즘

두 알고리즘 모두 공통적으로 참조(Reference) 라는 개념이 핵심이다.

1. 참조 횟수(Reference Counting)

참조 횟수 알고리즘에서는 어떤 객체도 참조하지 않는 객체 를 가비지 컬렉션 대상으로 본다.

아래의 예제를 차례로 살펴보며 이해해 봅시다.

var o1 = {
  o2: {
    x: 1
  }
};
// 두 객체가 생성됨
// 'o2'는 'o1'이 자신의 속성으로서 참조함
// 둘 다 가비지컬렉션 될 수 없음

var o3 = o1; // 'o3' 변수는 'o1'이 가리키는 오브젝트에 대한 참조를 갖는 두 번째임

o1 = 1;      // 이제 'o1'에 있던 객체는 하나의 참조만 남게 되고
             // 그것은 'o3' 변수에 들어 있음

var o4 = o3.o2; // 'o2' 속성에 대한 참조
                // 이제 이 객체는 두개의 참조를 가짐. 하나는 속성으로서 
                // 다른 하나는 'o4' 변수로서

o3 = '374'; // 원래 'o1'에 있던 객체는 이제 참조를 하는 곳이 없음 
            // 따라서 가비지컬렉션 될 수 있음
            // 하지만 'o2' 속성은 'o4' 변수가 참조하므로 가비지컬렉션 될 수 없음

o4 = null; // 원래 'o1'객체 내에 있던 'o2'속성은 이제 참조하는 곳이 없으므로
           // 가비지컬렉션 될 수 있음

보기에는 간단하고 효과적일 것 같은 참조 횟수 알고리즘은 순환 참조를 다루는 데는 어느정도 한계가 있다. 아래의 예제는 두 객체가 서로 참조하는 속성으로 생성되어 순환 참조를 이룬다. 일반적으로 함수 호출이 완료되고 나면 두 객체는 불필요해지기 때문에 할당된 메모리가 회수되어야 하지만, 두 객체가 서로 참조하고 있기 때문에 둘 다 가비지 컬렉션 대상이 되지 않는다.

function f() {
  var o1 = {};
  var o2 = {};
  o1.p = o2; // o1은 o2를 참조함
  o2.p = o1; // o2는 o1을 참조함. 이를 통해 순환 참조가 만들어짐.
}
f();

2. 표시하고 쓸기(Mark and Sweep)

표시하고 쓸기 알고리즘은 2012년 이후의 모든 최신 브라우저에서 채택하고 있는 가비지 컬렉션 알고리즘이다. 이 알고리즘은 가비지 컬렉션 대상을 해당 객체에 닿을 수 있는지 없는지를 판단하여 선택한다.

마크스위프 알고리즘은 아래의 세 단계를 거쳐 진행된다.

  1. 루트(Root) 목록 지정 및 표시
  2. 루트와 연결된 자식 표시
  3. 표시되지 않은 모든 메모리 OS에 반환

루트(Root) 목록 지정 및 표시

이 알고리즘에서 Root는 가비지 콜렉터의 시작점이다. 일반적으로 루트는 코드에서 참조되는 전역 변수다. 자바스크립트에서 window, Node에서는 global에 해당한다. 가비지 컬렉터는 맨 처음 작업으로 이러한 루트의 목록을 생성한다.

루트와 연결된 자식 표시

모든 루트와 자식들을 검사해서 활성화 여부를 검사한다. 여기서 활성화 여부는 루트와 자식간의 참조가 형성되어 있는지 아닌지의 여부를 뜻한다. 루트가 닿을 수 없는 자식들을 가비지 대상으로 표시한다.

Untitled

표시되지 않은 모든 메모리 OS에 반환

가비지 컬렉터가 이제 활성 표시가 없는 모든 메모리를 OS에 반환한다.

Untitled 1

이 알고리즘은 이전의 알고리즘인 참조 횟수 알고리즘에 비해 순환 참조 문제를 해결할 수 있다는 장점을 지니고 있다. 순환 참조의 경우 결국 루트에 연결되지 않은 상태이기 때문에 Sweep의 대상이 된다.

Untitled 2

순환 참조의 문제는 해결했지만, 여전히 자바스크립트에는 명시적으로 가비지 컬렉션을 작동할 수 있는 방법은 없다.

결론

자바스크립트에서 가비지 컬렉터는 우리가 조종할 수 없는 영역에 있다. 이로인해 생기는 편의성이 크지만, 종종 우리가 의도치 않은 메모리 낭비를 만들 수 있게 된다. 가비지 컬렉터가 어떠한 방식으로 동작하는지를 이해한다면, 이러한 문제를 어느정도 예방할 수는 있다. 특히 참조에 관하여 더 자세히 이해하고 개발 할 때 유념하여 개발하는 것이 더 나은 성능의 프로덕트를 만들 수 있다.

질문

  • 메모리 생존 주기에 대해 설명하시오.
  • JS에서 메모리 할당은 ___와 ___에 의해 발생한다.
  • [참조 횟수 / 표시 쓸기] 알고리즘은 순환 참조의 문제를 해결한다.
  • 마크스위프 알고리즘이 작동하는 3 단계에 대해 설명하시오.

Reference