Algorithm|Javascript Algorithm

자바스크립트 가비지 콜렉션의 기준

0

크리스는 1장에서 자바스크립트 엔진에 **가비지 콜렉터(Garbage Collector, 이하 GC)**라는 자동 청소부가 살고 있다는 사실을 알았다.

하지만 여전히 의문이 남는다.

"청소부가 내 마음을 읽는 독심술사는 아니잖아. 내가 '앞으로 이 변수는 안 쓸 거야'라고 생각만 했는데, 그걸 어떻게 알고 지우지?"

만약 GC가 엉뚱한 데이터를 "쓰레기"라고 착각해서 지워버리면 프로그램은 에러를 뿜으며 죽을 것이다. 반대로 쓰레기를 안 치우면 메모리가 터진다.

그래서 GC에게는 아주 엄격한 **"쓰레기 판별 기준"**이 있다.

오늘은 과거에 쓰였던 방식과 그 치명적인 약점, 그리고 현재의 표준 기준인 **도달 가능성(Reachability)**에 대해 알아보자.

1. "필요 없음" vs "도달할 수 없음"

우리는 흔히 "필요 없는 메모리"를 해제한다고 말한다. 하지만 컴퓨터 입장에서 "필요 없다"는 말은 너무 주관적이다.

그래서 자바스크립트는 기준을 바꿨다.

"루트(Root)에서 시작해서 닿을 수 있는가?"

  • Root(뿌리): 태초에 존재하는 전역 객체(window, global)나 현재 실행 중인 함수의 변수들이다. 이들은 절대 지워지지 않는다.
  • 도달 가능성(Reachability): 루트에서 참조(화살표)를 따라가서 닿을 수 있는 객체는 **"살아있다"**고 간주한다. 반대로, 루트에서 연결이 끊겨 둥둥 떠다니는 섬은 **"쓰레기"**로 간주한다.
  • 2. 과거의 기준: 참조 카운팅 (Reference Counting)

    초창기 알고리즘이나 일부 오래된 브라우저(IE 6, 7 등)는 참조 카운팅이라는 단순한 방식을 썼다.

    "나를 가리키는 화살표 개수가 0개면 쓰레기다."

    아주 직관적이다. 누군가 나를 보고 있다면 나는 필요한 존재고, 아무도 나를 안 보면 나는 잊힌 존재라는 것이다.

    JavaScript

    // 객체 생성 (참조 카운트: 1) - 'user' 변수가 바라봄 let user = { name: "Chris" }; // 다른 변수도 바라봄 (참조 카운트: 2) let admin = user; // user 변수가 손을 뗌 (참조 카운트: 1) - 아직 admin이 잡고 있으니 안 지워짐 user = null; // admin 변수도 손을 뗌 (참조 카운트: 0) -> 🗑️ 수거 대상! admin = null;

    이 방식은 구현이 쉽고, 카운트가 0이 되는 순간 즉시 메모리를 회수할 수 있다는 장점이 있었다. 하지만 치명적인 약점이 하나 있었다.

    3. 치명적 약점: 순환 참조 (Circular Reference)

    크리스가 결혼 정보를 담는 객체를 만들었다고 가정해보자.

    JavaScript

    function marry(man, woman) { woman.husband = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "Chris" }, { name: "Jane" });

    여기서 Chris와 Jane은 서로를 가리키고 있다. (husband, wife)

    이때, 외부에서 이 가족에 대한 참조를 끊어버리면 어떻게 될까?

    JavaScript

    family = null; // 외부 연결 끊음

    우리의 상식으로는 family가 없어졌으니 Chris와 Jane도 메모리에서 사라져야 한다.

    하지만 참조 카운팅 방식에서는 사라지지 않는다!

  • Chris의 입장: "Jane이 나를 husband로 가리키고 있네? 난 참조 카운트가 1이야. 난 살아있어!"
  • Jane의 입장: "Chris가 나를 wife로 가리키고 있네? 나도 참조 카운트가 1이야. 난 살아있어!"
  • 서로가 서로의 손을 잡고 있는 무인도에 고립된 커플과 같다.

    외부(Root)에서는 이들에게 갈 방법이 없는데, 자기들끼리 참조 카운트를 1로 유지하고 있어서 영원히 메모리에 남아있는다. 이것이 과거 인터넷 익스플로러(IE)에서 메모리 누수가 빈번했던 주원인이었다.

    4. 해결책: 관점을 바꿔라

    참조 카운팅의 한계 때문에, 모던 브라우저들은 **"개수 세기"**를 그만두었다.

    대신 서두에 말했던 **"도달 가능성(Reachability)"**을 기준으로 알고리즘을 변경했다.

    루트에서 시작해서 연결된 선을 따라가 본다.

    순환 참조로 자기들끼리 묶여 있어도, 루트에서 연결된 선이 없다면 도달할 수 없는 섬으로 간주하고 가차 없이 쓸어버린다.

    이 "도달 가능성"을 판단하기 위해 자바스크립트 엔진이 사용하는 구체적인 알고리즘이 바로 **표시하고 쓸기(Mark-and-Sweep)**다.

    핵심 정리

  • 가비지 콜렉터(GC): 메모리 관리를 대신해 주는 엔진 내부의 청소부.
  • 기준: 과거에는 **참조 카운팅(개수)**을 썼으나, 순환 참조(Circular Reference) 문제로 메모리 누수가 발생했다.
  • 현재: 모던 자바스크립트는 **도달 가능성(Reachability)**을 기준으로 한다. 루트(Root)에서 닿을 수 없으면, 자기들끼리 참조하더라도 쓰레기로 간주한다.
  • 그렇다면 GC는 구체적으로 언제, 어떻게 메모리를 훑으면서 "살아남을 자"와 "죽을 자"를 표시할까?

    브라우저가 멈칫하는 순간을 최소화하기 위한 고도화된 기술, Mark-and-Sweep의 동작 원리를 다음 장에서 자세히 알아보자.

    7장 자바스크립트에서 메모리 - 3. 표시하고 쓸기(Mark-and-Sweep) 알고리즘 편에서 계속된다.


    🔗 참고 링크

  • MDN - Memory Management (Circular References)
  • JavaScript.info - Garbage Collection
  • 댓글 (0)

    0/1000
    댓글을 불러오는 중...