이전에는 자바스크립트의 가비지 컬렉션에 대해서 알아보았다. 이번에는 JavaScript의 많은 변화를 준 V8 엔진의 자바스크립트에 대해서 알아보고자 한다.
가비지 컬렉션은 긍정적인 측면
- 프로그래머가 메모리를 더 이상 직접 관리할 필요가 없어졌기 때문에 유지보수가 간편해졌다.
- 장기간 실행되는 대규모 어플리케이션의 경우 많은 종류의 오류, 메모리 누수를 줄여주어 성능을 향상시켜준다.
그러나 가비지 컬레션을 사용한다는 것은 메모리 관리 방식에 대한 제어권을 포기한다는 의미이다. JavaScript의 경우 메모리 관리 방법에 대한 모든 제어 권한을 포기한다. ECMAScript의 가비지 컬레션은 인터페이스를 노출하지 않는다. 그래서 웹 앱이 메모리 사용량을 측정하거나 기비지 컬렉션에 대한 단서를 제공해주지 않는다.
가비지 컬렉션 언어의 성능은 가비지 컬렉션이 없는 것보다 더 좋거나 더 나쁘지도 않다. C언어는 나중에 객체를 해제해야 하는 경우 힙 기록이 더 복잡해지는 경향이 있어서 비용이 많이 들 수 있다. 관리되는 메모리에서 할당은 일반적으로 포인터를 증가시키는 것을 의미하지만, 메모리가 부족하여 가비지 컬레션이 시작되면 결국 비용을 지불해야 한다. 잘못 구현된 가비지 컬레션 시스템은 길고 예측할 수 없는 결과를 초래한다.
좋든 나쁘든 JavaScript에는 가비지 수집이 필요하다. V8에서는 성능이 꽤 좋고, 일시 중지가 짧고, 오버헤드 관리가 상당히 쉽다.
The basics
가비지 컬렉션이 해결하는 근본적인 문제는 메모리의 죽은 영역을 식별하는 것이다.
죽은 영역은 새 할당에 재사용하거나 운영 체제로 다시 릴리스 할 수 있다. 루트 객체 또는 다른 라이브 객체가 가리키는 객체는 라이브 객체이다. 루트 객체는 정의상 라이브이며, V8 또는 웹 브라우저에서 직접 가리키는 객체이다. 정의에 따라 라이브인 객체에서 일부 포인터 체인을 통해 도달할 수 있는 경우 객체는 라이브이다. 다른 모든 것은 죽은 객체로 분류된다.
- 라이브 객체: 여전히 프로그램에서 참조되고 있는 객체.
Heap organization
가비지 컬렉터의 내부 작업을 보기 전 힙 자체에 대해 알아보자. V8은 힙을 아래와 같이 여러 공간으로 나눈다.
- New-Space: 대부분의 객체는 여기에 할당된다. 다른 공간과 독립적으로 매우 빠르게 가비지 컬렉션이 가능하도록 디자인된 작은 공간이어서 매우 빠르게 가비지 수집되도록 설계되어있다.
- Old-Pointer-Space: 다른 객체를 가리키는 포인터가 있을 가능성이 있는 대부분의 객체가 포함되어 있다. 대부분의 객체는 new-space에서 어느정도 생존한 후에 이곳으로 이동한다.
- Old-Data-Space: 주로 데이터를 저장하는데 사용되는 공간이며, 포인터가 없는 데이터 구조를 위한 것이다. 원시값 (예: 숫자, 불리언, null, undefined)과 문자열, boxed 숫자, unboxed double 배열 등과 같은 원시 유형의 데이터를 저장하기 위해 사용된다.
- Large-object-Space: 크기가 제한된 다른 공간(Space)보다는 더 큰 객체가 여기에 할당된다. 각 객체가 자체 mmap'd (메모리 맵핑) 메모리 영역을 할당받으며, 큰 객체는 가비지 컬렉터에 의해 이동되지 않는다.
- Code-Space: 주로 JIT(Just-in-time) 컴파일러에서 생성된 코드 객체를 저장하는 공간으로 사용된다. 이러한 코드 객체는 주로 함수의 본문과 같은 실행 가능한 코드를 포함한다.
- Cell-Space, property-cell-Space and map-Space: 각 공간은 고정 크기의 객체를 저장하며, 특정 유형의 객체만을 포인팅할 수 있도록 제한이 있다. 해당 영역은 오랜 시간 동안 참조되면 Old-Space로 이동할 수 있다.
- Cell-space: 작은 크기의 원시 타입 데이터(예: 숫자, 불리언, null, undefined)들을 저장하기 위해 사용된다.
- Property-cell-space: 객체의 속성 값 (예: 문자열, 객체, 함수 등)을 저장하기 위해 사용된다.
- Map-space: 객체의 구조를 설명하는 메타데이터를 저장한다.
각 공간은 일련의 페이지로 구성이 된다.
- 페이지(page): 힙 메모리를 작은 조각으로 분할한 단위.
Discovering pointers
힙에서 포인터와 데이터를 구별하는 것은 가비지 컬렉션이 해결해야하는 첫번째 문제이다.
가비지 컬레션은 라이브 객체를 검색하기 위해 포인터를 따라야 하는데 대부분의 가비지 수집 알고리즘은 단편화를 줄이고 지역성을 높이기 위해 메모리의 한 부분에서 다른 부분으로 객체를 마이그레이션할 수 있고, 일반 기존 데이터를 방해하지 않고 포인터를 쓸 수 있어야 한다.
포인터를 식별하는 일반적인 세가지 접근 방식이 있다.
Conservative
C/C++을 위한 가비지 컬렉터인 Boehm-Demers-Weiser 가비지 컬렉터는 이 방식을 채택한다.
Conservative 가비지 컬렉션은 컴파일러의 지원이 없는 경우에 필요하다. 이 방식에서는 힙(heap)에 있는 모든 정렬된 워드(word)를 포인터로 취급한다. 이는 일부 데이터가 포인터로 취급될 수 있음을 의미한다. 그 결과로, 포인터처럼 보이는 정수가 객체의 큰 서브그래프(subgraph)를 유지할 때 이상한 메무리 누수(memory leak)가 발생할 수 있다. 또한, 우리가 포인터로 생각했던 데이터를 실수로 변경할 수 있으므로 객체를 메모리에서 옮길 수 없다. 이 결과로, 가비지 컬렉션의 압축(compression)을 통해 이점(더 간단한 할당, 더 작은 메모리 풋프린트, 더 나은 캐시 지역성)을 얻을 수 없다.
Compiler Hints
이 방식은 자바 가상 머신(JVM)에서 사용된다.
컴파일러 힌트 방식은 정적으로 타입이 지정된 언어에서는 컴파일러가 각 클래스 내부의 포인터 오프셋을 알려준다. 객체가 어떤 클래스에서 온 것인지를 식별할 수 있다면, 모든 포인터를 찾아낼 수 있다. 자바스크립트와 같은 동적으로 타입이 지정된 언어에는 잘 동작하지 않는다. 동적으로 타입이 지정된 언어에서는 객체 내의 모든 필드가 포인터 또는 데이터를 포함할 수 있기 때문이다.
Tagged Pointer
V8은 이 방식을 사용하며, OCaml과 같은 일부 정적으로 타입이 지정된 언어도 이 방식을 사용한다.
Tagged Pointer 방식은 각 워드의 끝에 포인터인지 데이터인지를 나타내는 비트를 예약한다. 이 방식은 제한된 컴파일러 지원을 필요로 하지만, 구현하기 간단하면서도 꽤 효율적이다.
- 워드(word): 메모리에서 처리되는 데이터의 기본 단위. 일반적으로 32비트 또는 64비트 크기를 가지며, 주로 CPU 레지스터와 메모리 연산에 사용된다.
V8은 32비트 워드로 나타낸다.
이 때 낮은 비트(low bit)는 아래 설정에 따라 의미가 달라진다.
- 0으로 설정되어 있으면, 포인터가 아닌 "원시 타입 데이터"로 표현된다.
- 01로 설정되어 있다면 "포인터"로 표현된다.
이 방식은 객체가 항상 적어도 4바이트 정렬(4-byte aligned)되어 메모리에 저장된다. 즉, 객체의 시작 주소는 항상 4의 배수이다. 이러한 정렬 방식 덕분에 낮은 2비트는 항상 0이 된다. 그렇기 때문에, 포인터를 표현할 때 낮은 비트를 태그로 사용하는 것이 가능해진다. 그렇기에 JavaScript의 객체 포인터는 항상 객체의 시작 주소를 가리키기 때문에 객체 중간 부분(일부분만)에 대한 포인터가 존재하지 않는다. 고로 메모리 관리와 가비지 컬렉션 성능이 향상되며, 실행 성능이 개선되고, 안전하게 동작할 수 있다.
힙(heap)에 있는 대부분의 객체는 태그가 지정된 워드(tagged word) 목록을 포함하고 있으므로, 가비지 컬렉터는 정수를 무시하고 포인터를 따라가며 이러한 객체들을 빠르게 스캔할 수 있다. 문자열과 같은 일부 객체는 포인터가 없다는 것으로 알려지 있으므로 태그가 필요하지 않다.
[참고]
'JavaScript' 카테고리의 다른 글
유데미(Udemy) "NodeJS 완벽 가이드" 수강 (0) | 2024.03.31 |
---|---|
[JS] V8: 전체 컴파일러 (1) | 2023.04.20 |
[Js RoadMap] 02. 가비지 컬렉션 (0) | 2023.04.06 |
[Js RoadMap] 01. JavaScript가 뭔데? (0) | 2023.03.27 |