가비지 컬렉션의 동작 원리

GonnabeAlright·2022년 5월 1일
0
post-thumbnail
post-custom-banner

참고: 자바스크립트 메모리 관리

let HEAP = [];

const A = {
  language: 'JavaScript'
}

const B = {
  language: 'Rust'
}

const C = {
 language: 'Elm' 
}

const D = {
  language: 'GoLang'
}

변수 HEAP를 힙 메모리 공간이라고 가정합니다. 상수 A에는 JavaScript가 B에는 Rust, C에는 Elm, D에는 GoLang 등 각각 문자열이 값으로 저장되어 있습니다.

HEAP.push(A);
HEAP.push(B);
HEAP.push(C);
HEAP.push(D);

HEAP 메모리 공간에 A부터 D순서대로 각각의 객체를 배열의 요소로 추가합니다.

 [ 
  { language: 'JavaScript', B: { language: 'Rust', C: { language: 'Elm', D: { language: 'GoLang' } } } },
  { language: 'Rust', C: { language: 'Elm', D: { language: 'GoLang' } } },
  { language: 'Elm', D: { language: 'GoLang' } },
  { language: 'GoLang' } 
 ]

다음과 같이 HEAP 메모리 공간에 저장되게 됩니다.

const root = () => HEAP[0];

A.B = B; // A 객체는 B객체를 참조
B.C = C; // B 객체는 C객체를 참조
C.D = D; // C 객체는 D객체를 참조

함수 표현식으로 작성된 root 함수HEAP 메모리의 최상단 요소를 가리킵니다. 각각의 객체는 HEAP 메모리에 다음 요소를 참조합니다. 따라서 A 객체의 BB 객체를 참조하고 B 객체의 CC 객체를 참조하고 C 객체의 DD 객체를 참조합니다.

const gc = () => {
  mark();
  sweep();
}

가비지 컬렉터의 역할을 하는 gc 함수는 mark 과정을 수행하는 mark 함수와 sweep 과정을 수행하는 sweep 함수를 각각 호출합니다.

const mark = () => {
  let reachables = [root()];
  
  while (reachables.length) {
    let current = reachables.pop();
    
    if (!current.__markBit__) {
      current.__markBit__ = 1;
      
      for (let i in current) {
       if (typeof current[i] === 'object') {
         reachables.push(current[i]);
       }
      }
    }
  }
}

mark 함수는 mark 과정을 수행합니다. 즉 HEAP 메모리 공간을 순회하면서 HEAP 배열의 요소에 값으로 1을 갖는 키 markBit를 추가합니다. root 함수는 HEAP 메모리의 최상단 요소를 반환하므로 reachables 변수는 HEAP 메모리의 최상단 요소를 갖는 배열이 됩니다. 이 변수를 반복문으로 돌면서 변수에 담긴 HEAP의 최상단 요소를 다시 current 변수에 담습니다. 만약 current 변수에 markBit가 존재하지 않는다면 아직 순회하지 않은 요소이므로 1을 값으로 갖는 키 markBit를 요소로 추가합니다, 이 때 current[i]가 객체일 경우 reachables 변수에 추가합니다.

✅ root가 접근할 수 없는 객체

여기서 current[i]HEAP의 최상단 요소부터 시작해 각 요소가 참조하고 있는 객체를 가리킵니다. 위에서 A는 B 객체를 참조하므로 B객체를 변수 reachables에 담고 B 객체는 C 객체를 참조하기 때문에 C 객체를 reachables에 담고 다시 C 객체는 D 객체를 참조하기 때문에 D 객체를 reachables에 담습니다. 만약 HEAP 최상단 요소가 아무 객체도 참조하고 있지 않으면서 그 아래 요소들이 다른 객체 요소를 참조하고 있는다고 해도 최상단 요소가 아무 값도 참조하고 있지 않기 때문에 반복문이 종료되어 reachables에 최상단 요소를 제외한 하위 요소들이 참조하는 객체들을 담지 않게 됩니다.

[
  {
    language: 'JavaScript',
    B: { language: 'Rust', C: [Object], __markBit__: 1 },
    __markBit__: 1
  },
  {
    language: 'Rust',
    C: { language: 'Elm', D: [Object], __markBit__: 1 },
    __markBit__: 1
  },
  {
    language: 'Elm',
    D: { language: 'GoLang', __markBit__: 1 },
    __markBit__: 1
  },
  { language: 'GoLang', __markBit__: 1 }
]

순회 결과 다음과 같은 값을 반환합니다.

const sweep = () => {
  HEAP = HEAP.filter(current => {
    if (current.__markBit__ === 1) {
      current.__markBit__ = 0;
      return true;
    } else return false;    
  });
}

const main = () => {
  gc();
}

main();

sweep 함수는 HEAP 메모리 공간 안에 존재하는 각 객체 요소들이 markBit를 가지고 있다면 markBit의 value를 0으로 바꿔 저장하게 됩니다. 즉 HEAP 메모리 공간에서 최상단 요소부터 시작하여 참조하거나 참조되는 객체들이 존재한다면 mark 함수를 호출한 결과로 markBit를 가지고 있을 것이며 이 markBit를 0으로 재할당하게 됩니다.

전체 코드

'use strict'

let HEAP = [];

const A = {
  language: 'JavaScript'
}

const B = {
  language: 'Rust'
}

const C = {
 language: 'Elm' 
}

const D = {
  language: 'GoLang'
}

HEAP.push(A);
HEAP.push(B);
HEAP.push(C);
HEAP.push(D);

const root = () => HEAP[0];

A.B = B; // A 객체는 B객체를 참조
B.C = C; // B 객체는 C객체를 참조
C.D = D; // C 객체는 D객체를 참조

const gc = () => {
  mark();
  sweep();
}

const mark = () => {
  let reachables = [root()];
  
  while (reachables.length) {
    let current = reachables.pop();
    
    if (!current.__markBit__) {
      current.__markBit__ = 1;
      
      for (let i in current) {
       if (typeof current[i] === 'object') {
         reachables.push(current[i]);
       }
      }
    }
  }
}

const sweep = () => {
  HEAP = HEAP.filter(current => {
    if (current.__markBit__ === 1) {
      current.__markBit__ = 0;
      return truel
    } else return false;    
  });
}


const main = () => {
  gc();
}

main();
post-custom-banner

0개의 댓글