sbyeol3 / articles

Learn.. Run.. 🏃
34 stars 1 forks source link

[번역] 자바스크립트의 가비지 컬렉션 #30

Open sbyeol3 opened 2 years ago

sbyeol3 commented 2 years ago

원문 : Garbage Collection in JavaScript

우리는 가비지(garbage)가 무엇인지 압니다. 가비지란 본질적으로 더 이상 필요없는 것을 의미합니다. 프로그래밍에서 가비지 콜렉션이란 사용하고 있지 않는 데이터의 메모리 공간을 정리하고 해당 메모리를 필요한 데이터의 메모리 공간으로 재할당합니다. 이는 거의 모든 프로그래밍 언어에서 가비지 컬렉션의 기본적인 과정입니다. 일부 프로그래밍 언어는 개발자가 직접 개입해야 하지만 어떤 언어들은 자동으로 실행됩니다. C언어 같이 저수준 프로그래밍 언어는 개발자가 더 이상 필요없는 변수나 객체들에 대해 malloc()free()와 같은 메소드를 사용하여 직접 메모리를 풀어주어야 합니다. 메모리를 해제하는 것은 개발자의 특권이며 메모리를 해제할 것인지를 결정하는 것은 개발자 손 안에 있습니다. 그러나 항상 그런 것만은 아닙니다. 자바스크립트와 같이 고수준 프로그래밍 언어에서는 메모리 관리 프로세스가 자동적으로 이루어집니다. 브라우저가 알아서 해줍니다.

이 섹션에서는 아래 내용을 살펴보겠습니다.

  1. 메모리 관리 라이프사이클
  2. 가비지 컬렉션 알고리즘
  3. 메모리 누수의 위험

관리 프로세스를 더 잘 이해하기 위해 먼저 메모리 관리의 일반적인 라이프사이클을 보겠습니다.

마지막 단계가 가비지 컬렉션 메커니즘이라고 불립니다. 자바스크립트에서는 자동으로 진행되며 가비지 컬렉터라고 불리는 개체가 수행합니다. 우리가 물리적으로 브라우저 엔진에서 직접 찾을 수는 없지만 말입니다. 가비지 컬렉터의 목적은 메모리 할당을 감시하고 특정 메모리가 더 이상 필요없는지 결정하는 것입니다. 더 이상 필요 없다면 메모리를 되찾습니다. 그러나 가비지 컬렉션 메커니즘은 할당된 메모리의 특정 블록이 유용하지는 결정할 수 없기 때문에 항상 근사치의 값을 가집니다.

자바스크립트에서 가비지 컬렉션 메커니즘은 아래 두 가지 알고리즘에 의해 제어됩니다.

  1. 참조 카운팅 알고리즘
  2. Mark and Sweep 알고리즘

하나씩 살펴봅시다.

참조 카운팅 알고리즘

먼저 참조가 무엇인지 이해해봅시다. 두 개의 객체가 주어졌을 때 한 객체가 다른 객체의 메소드나 속성에 접근할 수 있는 경우 이 객체는 다른 객체에 대한 참조값입니다. 그럼 알고리즘으로 옮겨갑시다. 참조 카운트 알고리즘은 매우 간단하고 다른 객체에 의해 참조되는지 아닌지에 따라 어떤 객체의 유용성을 결정합니다. 어떤 객체가 다른 객체에 참조되지 않는다면 가비지 값으로 간주하고 수집합니다. 간단한 예시는 아래를 보세요.

var obj = { // first object created 
 property1: { // second object created
 property2 : 10
 }
}

두 객체가 생성되었고 한 객체는 다른 객체의 프로퍼티를 참조합니다. 다른 객체는 obj 객체에 할당되어 참조됩니다. 참조를 하고 있으므로 가비지 컬렉션의 범위가 아닙니다.

var newObj = obj; // 존재하는 객체에 대한 새로운 객체가 있습니다.
obj = 10; // `obj`는 `newObj` 값에 의해 여전히 참조되므로 GC의 대상이 아닙니다.

다른 구현을 봅시다.

var anotherObj = newObj.property1;

property1은 두 개의 참조를 가집니다. 하나는 newObj 변수의 프로퍼티이고 다른 하나는 anotherObj 변수로부터 참조됩니다. 따라서 가비지 콜렉션의 대상이 아닙니다.

newObj = ‘Some string’;

obj 변수에 의해 생성된 객체는 더 이상 참조를 가지지 않습니다. 그러나 property1anotherObj 변수에 의해 참조되므로 가비지 컬렉션의 대상이 되지 않습니다.

anotherObj = null;

이제 어디에서도 property1 객체에 대해 참조하지 않습니다. 이 상화에서 객체는 가비지 컬렉션의 대상이 됩니다.

참조 카운팅 알고리즘은 본질적으로 너무 나이브해서 완전히 의존할 수는 없습니다. 아래의 예에서 참조 알고리즘의 힘이 크지 않습니다.

function foo() {
   var obj1 = {};
   var obj2 = {}; 
   obj1.a = obj2; 
   obj2.a = obj1;
   console.log(obj1);
   console.log(obj2);
}
foo();

출력되는 객체를 통과해보세요. 중첩된 속성들이 보이시나요? 이는 순환 참조라고 불리는 것입니다. 이 경우에서 참조 카운팅 알고리즘은 서로를 참조하는 중첩된 객체들에게 처참히도 실패합니다. 따라서 이 알고리즘을 사용했을 때 가비지 컬렉션을 할 가능성이 없습니다. 이는 다음에 살펴볼 Mark and Sweep 알고리즘으로 이어집니다.

Mark and Sweep 알고리즘

Mark and Sweep 알고리즘은 객체들에 도달하는 방법으로 GC 메커니즘을 구현합니다. 어떤 객체가 도달할 수 없는 거라면 가비지로 인식하여 수집됩니다. 알고리즘은 어떤 객체가 전혀 참조되지 않는다면 도달할 수 없다는 원리를 따르기 때문에 해당 객체를 가비지로 취급합니다. 이 과정은 window와 같이 루트 객체를 식별하고 거기서부터 모든 자식 객체들, 그리고 객체들의 자식들을 순회합니다. 이 과정에서 도달할 수 없는 일부 객체들이 있다면 그 객체들은 가비지로 수집되고 메모리를 해제합니다. 이 알고리즘은 효과적으로 순환 참조 문제를 해결합니다.

메모리 누수의 문제

개발자들은 메모리 누수를 방지하기 위해 많은 것들을 합니다. 메모리 누수를 일으키는 아주 일반적인 원인들은 아래와 같습니다.

  1. 지나치게 전역변수를 많이 생성하고 로컬 스코프에서 var 키워드를 생략하여 사용하는 경우
  2. setInterval()과 같은 타이머를 클리어하지 않는 경우
  3. 클로저를 불필요하게 사용하는 경우 (클로져는 부모 함수가 다 실행됐음에도 부모 함수로부터 변수를 받아 항상 참조함)

따라서 우리는 위와 같은 실수들을 피해야만 합니다.

이 글은 자바스크립트에서 굉장히 간단하게 가비지 컬렉션 과정을 살펴봤습니다. 이 토픽은 굉장히 방대하지만 더 큰 지식을 위한 출발점입니다.