[컴퓨터 공학] 가비지 컬렉션 (Javascript)

💡 메모리 관리가 필요한 이유

대부분의 언어에서 메모리 라이프 사이클은 메모리 할당 → 메모리 사용 → 메모리 해제의 단계를 거친다.
C같은 low-level 언어의 경우 이 라이프 사이클을 개발자가 malloc()이나 free()를 사용하여 직접 관리를 해주어야 하지만 자바스크립트와 같은 high-level 언어는 대부분 Garbage Collection이라는 자동 메모리 관리를 사용하기 때문에 개발자가 별도의 신경을 쓰지 않는다.

💡 가비지 컬렉션(Garbage Collection)

  • 가비지컬렉션은 더이상 사용하지 않는 메모리를 발견하고 이를 자동으로 해제해주는 역할을 한다.
  • 가비지컬렉션이 자동으로 메모리 관리를 해준다고해서 개발자가 완전히 신경을 쓰지 않는다면 메모리 누수(memory-leak)가 발생할 수도 있다.

    메모리 누수란, 더이상 어플리케이션에서 사용하지 않는도 불구하고 메모리 해제가 되지 않는 상태를 말한다.

💡 가비지컬렉션의 주요 알고리즘

1. Reference-counting

  • 이 알고리즘은 어떠한 값에 대해서 어디에서도 참조(reference)하지 않고 있다면 GC는 이 값을 필요하지 않은 값으로 간주하고 이 값을 제거한다.

📌 Case(1)

// people 변수는 해당 object를 참조한다 .
  let people = {
    name: "Jane"
  };
  //null값을 재할당한다.
  people = null;

{name: "Jane"}이라는 값은 더이상 참조되지 않기 때문에 GC에 의해 메모리가 해제된다.

📌 Case(2)

  // people 변수는 해당 object를 참조한다 .
  let people = {
    name: "Jane"
  };
  //girl에 people의 참조중인 값의 주소를 할당한다.
  girl = people;
  //people에 null을 할당한다.
  people = null;

people은 더이상 {name: "Jane"}을 참조하고 있지 않지만, girl은 참조하고 있기 때문에 해당 object는 여전히 메모리에 남아있게 된다.

📌 Case(3)

// people 변수는 해당 object를 참조한다 .
let people = {
  name: "Jane"
};
//girl에 people의 참조중인 값의 주소를 할당한다.
girl = people;
//people에 null을 할당한다.
people = null;
//girl에 null을 할당한다.
girl = null;

만약 위의 코드처럼 people, girl에 null을 할당하고 나면 {name: "Jane"}를 참조하고 있는 곳이 없기 때문에 해당 값은 GC에 의해 처리된다.

📣 하지만 reference-counting 방식은 순환참조(Circular reference)가 이루어지는 경우 메무리 누수의 요인이 된다는 문제점이 있다. 아래 예시 코드를 보자.

  function couple() {
      const jane = {};
      const sam = {};

      // jane.bf는 sam을 참조한다
      jane.bf = sam;

      // sam.gf는 jane을 참조한다
      sam.gf = jane;

      return 'circular';
  }

  couple();

couple()함수가 호출되고 끝나서 더이상 필요한 값이 아닌데도 불구하고 서로에 대한 참조가 걸려있기 때문에 GC는 이 값들에 대한 메모리를 해제하지 않아 계속해서 메모리에 남아 있는다. 이러한 순환참조는 메모리 누수의 주된 요인이라고 할 수 있다.


2. Mark-and-sweep

위의 reference-counting의 문제점으로는 불필요한 값임에도 불구하고 어디에선가
참조가 걸려있다면 이에 대한 메모리를 해제하지 않는다는 문제점이 있었다. 반면에 Mark-and-sweep 알고리즘은 이 값이 참조되고 있는 값인지에 중점을 두지않고 도달가능성(reachablility)에 중점을 둔다.

  • 도달 가능성이란 자바스크립트의 root라는 글로벌 object에서부터 시작하여 참조되는가에 대한 여부이다. 다시말해 root에서 부터 해당 값까지 도달이 가능한가에 대한 여부이다.

따라서 어떤 값에 대한 참조가 없는 경우는 당연히 도달이 불가능하기 때문에 메모리가 해제되어야 하는 값으로 여겨지고, 참조 되고 있다고 하더라도 root로부터 도달할수 없다고 여겨진다면 처리된다.

2012년부터 모던 브라우저들은 mark-and-sweep 방식의 GC를 사용하고 있다. reference-counting 방식에서 문제가 되었던 아래의 예시를 다시 살펴보자.

function Couple() {
    const jane = {};
    const sam = {};

    // jane.bf는 sam을 참조한다
    jane.bf = sam;

    // sam.gf는 jane을 참조한다
    sam.gf = jane;

    return 'circular';
}

Couple();

예시에서 Couple() 이라는 함수 가 호출된 후 'circular'값이 return 되고 끝난 후에는 더이상 root에서 jane과 same에 도달할 수 없기 때문에 해당 값들은 GC에 의해서 메모리가 해제된다.


🔎 살펴보기

  • 어떠한 값이 참조된다고해서 root에서부터 도달가능한 것은 아니다.
  • reference-counting의 순환 참조 문제점을 mark-and-sweep방식이 보완할 수 있다.
  • 가비지 컬렉션은 엔진이 자동으로 수행하므로 개발자는 이를 억지로 실행하거나 막을 수 없습니다.
  • 객체는 도달 가능한 상태일 때 메모리에 남습니다.
  • 참조된다고 해서 도달 가능한 것은 아닙니다. 서로 연결된 객체들도 도달 불가능할 수 있습니다.

참조링크

좋은 웹페이지 즐겨찾기