Node.js GC

Vorhandenheit ·2022년 1월 23일
0

JS/Node 

목록 보기
29/63

GC (Garbage Collection)

가비지 컬렉션은 프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것입니다.

1. 방법

자바스크립트는 두 개의 가비지 콜렉션 방법을 사용합니다.

(1) Refrence-counting

한 객체를 참조하는 변수의 수슬 추적하는 방법입니다.
객체를 참조하는 변수는 처음에는 특정 메모리에 대해 하나뿐이지만, 변수의 레퍼런스가 복사될 때마다 레퍼런스 카운트가 늘어납니다. 객체를 참조하고 있던 변수의 값이 바뀌거나, 변수 스코프를 벗어나면 레퍼런스 카운트는 줄어듭니다. 레퍼런스 카운트가 0이 되면, 그 객체와 관련한 메모리는 비울 수 있습니다.

(2) Tracing

한 객체에 flag를 두고, 가비지 컬렉션 사이클마나 flag 표시 후 삭제하는 mark and sweep 방법 입니다. 객체에 in-use flag를 두고, 사이클마다 메모리 관리자가 모든 객체를 추적해서 사용 중인지 아닌지를 표시합니다. 그후 표시되지않은 객체를 삭제하는 단계를 통해 메모리를 해체합니다.

Mark and Sweep

  • Mark
    객체가 생성될 때마다 mark bit 0 (false)로 설정되어집니다. Mark 단계에서 모든 접근 가능한 객체의 mark mit가 1(true)로 설정되어집니다.

  • Sweep
    Mark 단계 후에 mark bit가 여전히 0 (false)로 설정된 객체들은 도달할수 없는 객체이므로 가비지 콜렉터가 수집해 메모리에서 해제됩니다.

3 가지 상태

  1. White : 아직 가비지 컬렉터가 탐색하지 못한 상태
  2. Grey : 가비지 컬렉터가 탐색했으나, 해당 객체가 참조하는 개체들은 탐색하지 못한 상태
  3. Black : 가비지 컬렉터가 탐색했고, 해당 객체가 참조하는 객체들도 탐색 완료한 상태

구동 과정

  • marking => Sweep => Compact 과정으로 이루어집니다.
let x = {
  a: {
    b: 2
  }
}

let y = x
x = 1

let z = y.a.b
y = 'choongman'

z = null
    1. Marking
      1) roots를 모두 회색으로 마킹하고, Deque에 push 합니다.

      2) Deque에서 pop front하여 객체를 꺼내어 검은색으로 마킹합니다.(탐색완료)

      3) 검은색으로 마킹된 객체가 참조하는 객체들을 회색으로 마킹하고, push front합니다,

      4) Deque가 완전히 빌 때까지 b, c를 반복합니다.

      5) 최종적으로 검은색과 흰색으로 분류되고 Deque는 완전히 비게딥니다.

    1. Sweep
      분류가 끝났으니 흰색으로 마킹된 객체들을 가비지로 인식하고 메모리를 해제합니다.
    1. Compact
      메모리의 파편화가 심해지지않도록 메모리를 재배치하여 메모리를 확보합니다.

구동 방식

이러한 mark-sweep-compaction을 할 경우, heap이 클 경우 한번에 실행하면 수백 밀리초를 소모하게 됩니다.

이를 해결하기위해서 점진적인(incremental)마킹으로 전환했습니다.
incremental : 점진적 마킹을 수행하는 동안 garbage collector는 마킹 작업을 더 작은 덩어리들로 쪼개고 그들 사이사이에 어플리케이션이 실행될 수 있도록합니다.

이렇게 할경우, heap 객체 그래프를 수정했다고 했을 때, GC가 완료되기전에 수정될 수 있다는 문제가 존재합니다. 그래서 이때 'Write Barrier를 이용해 처리를 진행합니다.
객체가 어떤 객체를 참조할 때, Write Barrier를 통해 Old => New 포인터로 정보를 기록하는 것 뿐만 아니라 검은색 객체가 흰색 객체를 참조하는 것도 기록합니다. Marking 부분적으로 진행하면서 검은색=> 흰색 참조를 하는 객체를 발견하면, 처리하지않고 회색으로 변경하여 다시 Marking Worklist에 추가합니다.

Marking Worklist
Major GC의 Marking Queue

  • Parralled Marking

    멀티스레드로 마킹을 진행하는 방법입니다. worker thread 는 heap 객체 그래프를 read하기만 하며, Marking worklist와 page의 Marking bitmap에 대해서만 read/write가 가능하도록 구성하는 겁니다.
    전역 Marking Worklist에 대해서는 Read/Write 락을 통해 동기화하며, 이를 Worker 스레드들이 부분적으로 가져가 각자 가지고 있는 Marking worklist에 넣고 처리한다.

Marking bitmap은 atomic 계산으로만 수정되므로 이에 대한 lock은 필요하지 않으며, 이 값을 통해 각 스레드에서 객체의 색을 구분해 처리한다.

만약 어떤 Worker Thread의 Marking Worklist에 객체가 너무 많다면 이를 전역 Marking Worklist에 보내 다른 스레드들이 처리하게 한다.

  • Concurrent Marking

    concurrent Marking은 worker thread가 heap에 있는 객체들을 방문하는 동안, 자바스크립트가 Main thread에서 실행될 수 있도록 해줍니다.
  • 결과 (Incremental + Concurrent + Parallel Marking)

    concurrent 마킹을 존재하는 점진적인 마킹 하부구조로 통합했습니다.
    main thread는 루트를 스캔함으로 마킹을 시작하고, Marking worklist를 채웁니다.
    worker thread는 main thread를 도와서 marking worklist를 소모시킴으로 마킹 처리를 더욱 빠르게 합니다.
    marking worklist가 비어있게 되면, main thread는 garbage collection을 완료합니다. 완료하는 동안 main thread는 루트를 다시 스캔하고 추가적인 흰색 객체들을 발견할 수도 있습니다. 이 객체들은 worker thread 도움으로 paralled하게 마킹됩니다.

2. G.C

가비지 콜렉션이 어떻게 동작하는지 알기전에, Heap과 stack에 대해서 알아야합니다

(1) HEAP

Heap : 객체, 문자열 또는 클로저와 같은 참조 유형을 저장합니다.
참조유형은 객체, string, 클로저 등을 의미합니다.

function Car(arg) {
	this.name = arg.name;
}

const McQueen = new Car({ name: 'McQueen'});

이 코드에서 Car객체는 힙에 생성됩니다.

힙에는 New Space, Old Space 두 개의 주요 세그먼트를 가지고 있습니다.

Scavenge : 이 가비지 컬렉터는 실행될 때마다 메모리의 작은 부분을 청소하여 young generation을 처리합니다. Minor GC라고도 합니다.

A. New Space

New Space는 새로운 할당이 일어나는 곳입니다. 이 곳에 존재하는 객체를 Young Generation이라고 부릅니다.

B. Old Space

Old SpaceNew Space에서 GC로 살아남은 객체들이 이동하게 되어집니다.
Old Space에서 존재하는 객체를 Old Generation이라고 합니다.

포인터만 모아놓은 Old pointer space, 데이터만 모아놓은 Old data space로 나누어집니다.
Major GC가 메모리를 관리합니다.
일반적으로 ~20% 가량 Young Generation이 살아남아 Old Generation이 됩니다.

포인터
자바스크립트에서 메모리상 포인터와 데이터를 구분하는 방법은 Tagged Pointers방법입니다.

(2) STACK

  • STACK : 객체에 대한 기본 유형과 참조를 포함합니다. 즉 heap 에서 생성된 객체에 대한 참조가 저장되는 곳입니다.

(3) Minor GC 동작과정

  1. 새로운 객체로 인해 New spce 공간이 초과되는 상황
  • New Space는 To-space와 From-space로 나누어져있습니다. 객체는 To-space에만 저장합니다.
  • 새로운 객체가 들어와야하는데, To-space 메모리공간을 초과하게되는 상황입니다.
  1. To-space와 From-space의 역할 변경 및 객체 이동

  • From-space는 평소에 메모리가 전부 비어있습니다.
  • To-space가 가득차는 상황이 되면, 두 공간의 역할이 바뀝니다.
  • From-space에 있는 개체들 중 참조를 당하고 있는 객체들만 To-space로 이동합니다.(가비지 컬렉션)
  • 참조가 끊어진 객체들은 제거합니다.
  1. 새로운 객체는 메모리가 확보된 To-space에 추가됩니다.

  2. 다시 To-space에 메모리가 초과됩니다.

  1. 2번 이상 살아남은 객체는 Old space로 이동합니다.
  • 1, 5번 객체는 가비지 컬렉션에서 살아남았으므로 Old space로 이동합니다.
  1. 결과
  • To-space와 From-space 2개의 메모리 공간의 역할을 바꿔가며 가비지 컬렉션 실행하는 방법을 '스캐벤저'라고 합니다.

(4) Write Barriers

Old space의 객체가 New Space 객체를 참조하고 있는 상황일 때, Minor GC가 발생해서 New Space의 객체가 삭제되면, 해당 객체를 참조하고 있는 Old GC의 객체는 삭제된 메모리의 객체를 참조하는 문제가 발생하게 됩니다.
그래서 어플리케이션에서 참조를 대입하거나 수정할 때, 해당 객체에 대해 '어떤 영역의 객체가 어떤 영역의 객체를 참조한다'는 정보를 기록해두는 방법을 사용하게 되었고, 이를 Write Barrier라고 합니다.

출처

https://hyunjun19.github.io/2018/02/19/node-js-at-scale-node-js-garbage-collection/
https://yceffort.kr/2020/12/javascript-garbage-collection
https://blog.risingstack.com/finding-a-memory-leak-in-node-js/
https://ui.toast.com/weekly-pick/ko_20200228

profile
읽고 기록하고 고민하고 사용하고 개발하자!

0개의 댓글