JavaScript 메모리 공간
Heap (힙): JavaScript 객체 인스턴스가 할당되는 메모리 영역. V8 엔진에서는 힙을 관리하기 위해 각 객체의 할당과 해제를 추적, 필요할 때 가비지 컬렉션을 수행하여 사용하지 않는 객체를 해제.
Stack (스택): 함수 호출 시 지역 변수, 함수 매개변수 및 함수 호출 스택을 저장하는 영역. 각 함수가 호출될 때마다 해당 함수의 실행 컨텍스트가 스택에 push, 함수가 종료되면 pop.
--max-old-space-size
와 같은 CLI 옵션을 통해 제한 설정 가능.3 - 1. heapdump
메모리 덤프를 생성해 현재 힙 상태 분석하는 도구.
설치
npm install heapdump
사용법
const heapdump = require('heapdump');
// 메모리 덤프 생성
heapdump.writeSnapshot('/tmp/heapdump.heapsnapshot');
3 - 2. node-inspect
Chrome 개발자 도구와 연계해 실시간으로 node js 메모리 상태를 모니터링할 수 있다.
사용법
node --inspect app.js
3 - 3. profiler
CPU, 메모리 사용량 등을 분석하는 도구.
다음과 같은 기능 제공.
예를 들어, Chrome 개발자 도구의 프로파일러를 사용하거나, Node.js의 --prof 옵션을 활용하여 프로파일링 데이터를 수집할 수 있습니다.
V8에서 GC는 힙 영역의 new space와 old space에서 일어나고 각각의 영역에서 GC는 서로 다르게 동작한다.
본격적으로 GC에 대해 설명하기 전에 The Generational Hypothesis 에 대해 알아야 한다.
The Generational Hypothesis는 새로 만들어진 객체가 오래된 객체보다 쓸모없어질 가능성이 높다는 가설이다. 따라서 오래된 객체가 쓸모 없어질 가능성이 낮은데 GC가 모든 객체를 매번 검사하는 것은 비효율적이다.
New space 에는 2개의 semi space 가 있다. 그 중 하나를 From space, 다른 하나를 To space 라고 한다.
위의 그림처럼 From space에서 새로운 객체들이 있다가 마이너 GC에서 살아남은 객체들은 아래에 있는 To space로 대피를 하게 됩니다.
이렇게 To space로 이동하는 과정을 통해 메모리 단편화가 줄어들게 됩니다.
객체 복사 과정에서 참조 업데이트가 함께 이루어져 접근 속도가 빨라진다.
이렇게 마이너 GC에서 살아남은 객체들이 다 살아남았다면 From space의 모든 객체들을 비우고 To space가 된다. 그리고 기존의 To space는 From space가 된다.
이제 다시 새로운 객체를 할당했을 때, From space의 다음 빈주소에 할당된다.
마이너GC에서 2번 생존한 2개의 객체는 Old space로 이동된다.
새로 수집된 객체는 1번 생존하면 To space로 이동한다.
Old space에서 진행되는 메이저 GC는 아래와 같이 2가지 알고리즘을 이용한다.
기본적인 로직은 참조되지 않는 객체를 쓸모없는 객체로 간주하며 진행된다.
메이저 GC는 크게 보면 3가지 단계를 걸쳐서 동작한다.
이제 이 단계를 알아보자
마킹
마킹 과정은 Roots 라는 실행 스택과 전역 객체를 담고 있는 객체의 set에서 시작되어 DFS를 이용해 순회하면 Tri-color로 마킹한다.
스위핑
흰색의 객체들을 free-list라는 자료구조에 추가. ( 이 주소 공간에 다른 값을 저장할 수 있도록함.)
압축
메모리 단편화가 심한 페이지들을 재배치 → 추가적인 메모리 확보.
위에서 말하는 마이너 GC, 메이저 GC를 통해 가비지 컬렉션이 수행할 때 프로그램이 멈추게 된다.
이를 stop-the-world라고 한다. 이렇게 멈추는 시간이 길어질수록 UX가 매우 나빠지게 된다.
이를 해결하기 위해 Orinoco 프로젝트를 통해 GC이 발전했고 아래는 간단하게 설명한 최신 GC들이다.
그 동안 JavaScript 가비지 컬렉팅에 대해 '엔진에서 자동으로 진행하기 때문에 따로 메모리를 해제할 필요가 없다' 이정도만 알고 있었는데 이번 기회에 V8엔진에서 진행되는 가비지 컬렉팅에 대해 확실하게 이해할 수 있었고, 그것과 더불어 전에 블로그 글에서 작성했듯 참조형 변수와 기본형 변수가 어떻게 저장되는지는 알았는데 그것을 스택과 힙 구조와 합쳐서 이해하지는 못했는데 이번 가비지 컬렉팅을 정리하며 기존에 알고 있던 지식과 합쳐서 확실하게 이해할 수 있었다.