JavaScript Garbage Collection

최제원·2024년 7월 22일

나의 Process글에서 Memory구조가 등장하는데 해당 예제 코드인 저수준 언어인 C언어에선 Malloc함수와 Free함수로 메모리를 관리해주고 있는 것을 코드레벨로 확인하였다
반대로, 나는 JavaScript 고수준 언어를 사용하는 개발자이며 직접 메모리 관리를 해준적은 한번도 존재하지 않았다 그렇다면 자바스크립트에서는 어떻게 메모리를 관리하고 있을까에 대하여 알아보자

메모리 생존 주기

Mdn에선 메모리의 생존주기는 대부분의 프로그래밍 언어에서 엇비슷하다고 얘기하고 있다.

  1. 필요할 때 할당합니다.
  2. 할당된 메모리를 사용합니다. (읽기, 쓰기)
  3. 더 이상 필요하지 않으면 해제합니다.

두 번째 부분은 모든 언어에서 명시적으로 사용된다(CRUD) 그러나 첫 번째 부분과(할당) 마지막 부분(해제)은 저수준 언어에서만 명시적이며
JavaScript와 같은 대부분의 고수준 언에서는 암묵적으로 작동한다

1.할당

자바스크립트는 변수를 선언할 때 자동으로 메모리에 할당한다,
원시 타입의 값을은 Stack영역에 저장되며,
참조 타입은 Heap영역에 저장되며, 그 주소값은 스택영역에 저장된다.
또한 변수 식별자는 스택 영역 상의 실행 컨텍스트의 렉시컬 환경에 저장된다.

const name = 'choi'; // primitive 타입은 메모리 스택 영역에 저장

const names = ['dan', 'kkancho']; // 배열, 함수, 객체는 힙 영역에 저장

2.사용(참조)

console.log(name) // choi
consoe.log(names[0]) // dan

v8 엔진에 의해 전역 실행 컨텍스트 렉시컬 환경에 있는 식별자 a, b를 참조합니다.

3.해제

C언어, C++언어처럼 자바스크립트에서는 수동으로 쓸모없는 메모리 영역을 삭제할 수 없다
위의 대부분 고수준 언어에서는 암묵적으로 작동한다가 해당 의미이다
그렇다면 자바스크립트는 어떻게 사용하지 않는 메모리를 해제할까
어떻게에 해당되는 방법이 바로 가비지 컬렉터(GC)이다

1. 참조-세기(Reference-counting) 가비지 콜렉션

let x = {
  a: {
    b: 2,
  },
};
// 2개의 객체가 생성되었습니다. 하나의 객체는 다른 객체의 속성으로 참조됩니다.
// 나머지 하나는 'x' 변수에 할당되어 참조됩니다.
// 명백하게, 가비지 콜렉션 수행될 메모리는 하나도 없습니다.

let y = x;
// 'y' 변수는 위의 객체를 참조하는 두 번째 변수입니다.

x = 1;
// 이제 'y' 변수가 위의 객체를 참조하는 유일한 변수가 되었습니다.

let z = y.a;
// 위의 객체의 'a' 속성을 참조했습니다.
// 이제 이 객체는 두 개의 참조를 가집니다.
// 'y'가 속성으로 참조하고 'z'라는 변수가 참조합니다.

y = "mozilla";
// 이제 맨 처음 'x' 변수가 참조했던 객체에 대한 참조가 없기 때문에, 가비지 컬렉션이 가능합니다.
// 그러나, 객체의 'a' 속성이 여전히 'z' 변수에 의해 참조되므로
// 메모리를 해제할 수 없습니다.

z = null;
// 이제 맨 처음 'x' 변수가 참조했던 객체의 'a' 속성에는
// 참조가 없으므로 가비지 콜렉션이 수행될 수 있습니다.

참조-세기 가비지 콜렉션(현재는 사용되지 않는다)의 알고리즘은 위와 같은 동작으로 이루어진다

GC에는 문제가 없어 보이는데 왜 현재는 사용되지 않을까?

순환 참조 문제

let foo = {} // 참조 카운트 1
let bar = {} // 참조 카운트 1

foo.bar = bar // 참조 카운트 2
bar.foo = foo // 참조 카운트 2

foo = null // 참조 카운트 1 (GC 의 컬렉션 대상에 포함되지 않음)
bar = null // 참조 카운트 1 (GC 의 컬렉션 대상에 포함되지 않음)

해당 foo, bar는 변수로도 참조하지 못하기 때문에 heap 메모리의 영원히 남게 된다
즉 메모리 누수가 발생하게 되고 성능 저하의 원인이 된다

2. 표시하고-쓸기(Mark-and-sweep) 알고리즘

번역이 이게 맞는지 싶기는 하다

  • GC root(자바스크립트의 global, 즉 window 객체이다)

위의 구조가 현재 자바스크립트가 채택하여 사용하고 있는 GC의 알고리즘이다
자세한 사항은 밑 자바스크립트 GC단에서 설명할 예정이다

V8 Engine GC

먼저 v8 엔진의 메모리 구조이다, 프로그램이 실행되면 메모리의 Resident Set의 빈 공간에 프로세싱 된다
Resident Set은 스택 영역, 힙 영역으로 나뉘어지며 자바스크립트의 경우 싱글쓰레드라서 스택 메모리를 하나만 갖게 됨
그 중 GC를 담당하는 영역은 에 해당 된다 이유는 stack영역은 함수 호출이 끝난 후 OS 레벨에서 정리가 된다고 한다
반대로 힙 메모리는 그렇지 못하기 때문에 GC가 메모리를 관리 해주어야 되고 메모리가 해제되지 않으며 heap메모리에 계속 남게 되면
메모리 누수가 발생하여 프로그램의 성능 저하가 발생함

  1. 새 객체는 New Space 영역의 semi space에 할당이 이루어지며 모든 할당이 이루어지면 Minor GC가 발생한다
  2. 살아 남은 객체는 나머지 sem space로 evacuation(대피)하게 된다

💡 From Space, To Space

To Space: 모든 객체들이 대피하여 비어있는 영역
From Space: 객체들이 머무르는 영역

마이너 GC에서 살아남은 객체들은 위에서 설명한것과 같이 대피를 하게 되는데 이 때 살아남은 객체들은 연속적인 메모리 주소를 갖어
메모리 단편화를 주기적으로 방지하여 준다고 한다, 그리고 객체는 새로운 메모리 주소값으로 포인트가 갱신된다. 해당 GC의 순서도는 아래와 같다.

  1. From Space에서 To Space로 생존한 객체들의 대피가 완료
  2. From Space의 더이상 참조되지 않는 객체들을 GC
  3. From Space와 To Space의 역할을 서로 스왑

위의 과정을 거쳤음에도 살아남은 객체들은 더 이상 new space에서 관리하지 않으면 Old space로 이동되어 관리 된다
New space는 메모리가 가득 찬 상황에 major gc가 이루어지는 반면, Old space의 major gc는 동적으로 이루어진다

Old Space Major Gc

  • 마킹
    특정 객체가 가비지 컬렉션의 대상인지 조사하는 단계 root 실행 스택과 전역 객체를 담고 있는 객체의 set부터 시작하여 dfs 순회
    • white: GC가 탐색하지 못한 상태(gif상 회색)
    • gray: 탐색은 하였으나, 해당 객체가 참조하고 있는 객체가 있는지 확인을 안 한 상태(gif상 노란색 A, F)
    • black: 해당 객체가 참조하고 있는 객체까지 확인을 한 상(gif상 파란색)

최종적으로 결국 모든 노드들은 black, white색을 띄우고 있게 되며 흰색은 GC의 대상이 된다

💡 스위핑, 압축

1. 스위핑
여전히 흰색으로 마킹된 객체들의 메모리 주소를 free-list라고 부르는 자료구조에 추가합니다. 이제 이 주소들의 메모리 공간은 사용 가능하여 새로운 객체가 저장 가능합니다.

2. 압축
메모리 단편화가 심한 페이지들을 재배치하여 추가적인 메모리를 확보합니다.

💡 메모리 단편화, 페이지

1. 메모리 단편화 (Memory Fragmentation)
메모리 단편화는 프로그램이 메모리를 할당하고 해제하는 과정에서 발생하는 현상으로, 사용 가능한 메모리 공간이 여러 개의 작은 조각으로 나뉘어져 있어 큰 메모리 블록을 할당할 수 없게 되는 상황을 말한다.

2.페이지 (Page)

페이지는 가상 메모리 관리의 기본 단위로, 운영체제가 물리 메모리를 효율적으로 관리하기 위해 사용하는 고정 크기의 블록. 페이지는 일반적으로 4KB 또는 8KB 크기로 설정되며, 가상 주소 공간을 물리 주소 공간으로 매핑하는 데 사용

참조

profile
Why & How

0개의 댓글