DCheck : DCHECK는 디버그 모드에서 조건을 검증하기 위해 사용되는 매크로로, C++ 코드에서 DCHECK 매크로가 사용된 위치에서 주어진 조건이 참(true)인지 확인. 만약 조건이 거짓(false)이라면, 프로그램 실행을 중단하고 디버거에 진입하거나 오류 메시지를 출력. 이는 코드의 무결성을 확인하고 디버그 빌드에서 오류를 조기에 발견하기 위해 사용
DCHECK(condition): 조건이 참인지 확인
DCHECK_EQ(val1, val2): val1이 val2와 같은지 확인
DCHECK_NE(val1, val2): val1이 val2와 다른지 확인
DCHECK_LE(val1, val2): val1이 val2보다 작거나 같은지 확인
DCHECK_LT(val1, val2): val1이 val2보다 작은지 확인
DCHECK_GE(val1, val2): val1이 val2보다 크거나 같은지 확인
DCHECK_GT(val1, val2): val1이 val2보다 큰지 확인
javascript는 단일 스레드 언어이며, service worker를 사용하면 worker당 새 V8 process를 생성한다. 실행중인 program은 V8 process에 할당된 일정량의 memory로 표현되고, 이 메모리 구조를 Resident Set이라 불린다. 아래의 도식도는 v8 engine에서의 memory management에 대한 도식도이다.
v8의 memory space는 stack과 heap으로 나뉘어지며 heap memory에 대해서는 목적에 따라 더 세분화된 space로 나뉘어진다.
숫자와 문자열과 같은 정적인 값은 순서대로 stack에 push되고, object value는 heap에 들어가게 되며, heap영역의 주소는 stack에 push된다. 이 방식이 일반적으로 stack과 heap에서의 일이 분리되는 방식이다.
stack memory
v8은 process당 하나의 stack을 가진다. 해당 영역에는 method/function frames, primitive value(기본 값), object를 가리키는 pointer가 저장된다. stack memory의 제한은 --stack_size라는 v8의 flag를 통해 설정할 수 있다.
heap memory
v8이 object나 dynamic data를 저장하는 공간이다. 해당 영역은 GC(Garbage Collection)가 위치하며 가장 큰 memory 영역이다. 모든 heap memory영역이 garbage collection이 일어나는 것은 아니고, 오직 young과 old space만이 gc에 의해 관리된다. 더욱 세분화해보자.
하기에 표현되는 "살아있다"는 해당 영역에 위치함을 나타낸다.
New Space: New space 혹은 "Young generation"은 새로운 object들이 살아있고, 대부분의 이 object들은 짧게 산다. 이 space는 크기가 작고, JVM의 S0 & S1과 같이 두개의 smie-space로 이루어져 있다. 이 공간은 추후에 살펴볼 "Scavenger(Minor GC)"에 의해 관리된다. New space의 크기는 --mini_semi_space_size(초기화)와 --max_semi_space_size(최대) v8 flag들에 의해 조절된다.
Old Space: Old space 혹은 "Old generation"은 "New space"에서 두번의 minor gc cycle을 거친 object들이 살아남아 이동하게 되는 곳이다. 이 space는 Major GC(Mark-Sweep & Mark-Compact)에 의해 관리된다. --initial_old_space_size(초기화)와 --max_old_space_size(최대) v8 flag들에 의해 조절된다. 이 공간은 두개로 나뉜다.
Old pointer space: 다른 object를 가리키는 pointer가 있는 살아남은 object를 포함한다.
Old data space: 오직 data(다른 object를 가리키는 pointer를 제외한)만을 가진 object를 포함한다. 문자열, boxed number, unboxed double형 배열은 두 번의 minor GC cycle 동안 "New space"에서 살아남은 후 여기로 이동한다. [ boxed number : 기본형(primitive type)을 객체(object)로 변환하는 것을 의미 (ex. 정수형(int)을 Integer 객체로 변환하는 것.), arrays of unboxed double : 기본형 더블(double) 타입의 배열을 의미 ]
Large object space: 다른 space의 크기 제한보다 큰 object가 있는 곳이다. 각 객체는 자체[mmap](https://en.wikipedia.org/wiki/Mmap)'d 메모리 영역을 갖는다. 큰 object들은 절대로 gc에 의해 이동되지 않는다.
Code-space: 이 곳은 Just In Time(JIT) compiler가 compile된 code block들을 저장하는 곳이다. 이 space는 실행가능한 memory가 위치하는 유일한 공간이다.(또한, code는 "Large object space"에도 위치 할 수 있으며, 이 역시 실행가능하다.)
Cell space, property cell space, and map space: 이 공간은 각각Cells,PropertyCells, maps를 포함한다. 각각의 공간들은 모두 같은 크기의 object를 포함하고 있으며 어떤 종류의 object를 가리키는지에 대한 제약이 있어 collection을 간소화한다.
program이 자유롭게 사용할 수 있는 크기보다(v8 flag설정에 따라) heap에 더 많은 memory를 할당하려고 할시에 out of memoryerror가 발생한다. 잘못된 heap관리는 memory leak으로 이어질 수 있다.
v8은 gc에 의해 heap memory를 관리한다. 간단히 말해, 더이상 stack에서 직간접적으로(다른 object의 reference) 쓰이지 않는 orphan object(고아객체)가 사용하는 memory를 해제하여 새 object생성을 위한 space를 만든다.
Orinoco는 main thread를 해제하기 위해 병렬, 증가 및 concurrent technique를 활용하기 위한 v8 gc project의
코드명이다.
v8의 gc는 v8 process에서 사용되지 않는 memory를 회수하여 재사용할 수 있도록 한다.
v8 gc는 generational하다.( heap의 object는 age에 따라 묶이며 다른 stage에서 지워진다.)
v8에는 gc에 활용되는 두 stage와 세가지 다른 알고리즘이 있다.
Minor GC(Scavenger)
이 타입의 gc는 young혹은 new generation을 compact하고 깨끗하게 유지한다. objetc는 상당히 작은( heuristic에 따라 1~8MB 사이 ) 새 space에 할당된다. new space에서의 할당은 매우 저렴(할당 비용이 저렴)하다. 새 object를 위한 공간을 예약할 때마다 증가하는 allocation pointer가 존재한다. allocation pointer가 새 space의 끝에 도달하였을 때, minor gc가 trigger된다. 이 process는 Scavenger라고도 하며 Cheney algorithm으로 구현된다. 자주 발생되며 병렬 helper thread를 사용하며 매우 빠르다.
새로운 space는 동일한 크기를 가진 ( to-space와 from-space로) 두개의 공간으로 나뉜다. 대부분의 allocation은 from-space에서 이루어진다(항상 old-space에서 할당되는 실행코드와 같은 특정 종류의 object 제외). from-space가 채워지면 minor gc가 trigger된다.
2. v8은 from-space에서 필요한 memory를 가져오려하지만 여유 space가 없는 경우 minor gc를 trigger한다.
3. minor gc는 gc의 root에서 시작하여 from-space에서 object graph를 재귀적으로 탐색하여 사용되거나 살아있는 object들을 탐색하고, 이러한 object들을 to-space로 이동한다. 이 때, 이러한 object를 참조하는 모든 object도 to-space의 page로 이동하고 해당 pointer를 업데이트한다. 이 작업을 from-space의 모든 object가 scan될 때 까지 반복하며, 해당 작업이 끝날 시에 to-space가 자동으로 압축되어 fragmentation이 감소한다(compaction).
4. 이제 minor gc는 현재 남아있는 모든 object는 garbage이므로 free시킨다.
5. minor gc는 to-space와 from-space를 교환하고, 이제 다시 모든 object는 from-space에 위치하며 to-space는 비어있다.
6. 새로운 object는 from-space에 할당되며 시간이 지나 from-space에 더 많은 object가 존재한다고 가정한다.
7. 또다시 여유 space가 없을 경우, minor gc가 trigger된다. 위의 process가 반복되고 살아남은 object들은 old space로 이동한다. 첫 minor gc에서 살아남은 object는 to-space로 이동하고, 나머지 garbage는 from-space에서 free된다.
8. to-space와 from-space를 교환하고 모든 object는 from-space에 있으며 to-space는 비어있는 상태이다.
9. 이제 다시 새 object는 from-space에 메모리를 할당한다.
이 과정은 stop-the-world process이지만, 매우 빠르고 효율적이기에 무시해될 정도의 수준이다.
Magor GC
이 타입의 gc는 old generation을 compact하고 깨끗하게 유진한다. old generation의기준은 scavenger에서 이루어지는 두번의 과정으로부터 살아남은 object들이다. 이는 v8이 old generation의 공간이 충분하지 않다고 판단될 때, trigger된다. 또한, cheney algorithm은 young generation과 같이 작은 공간에 효율적이기에 사용되지 않는다. magor gc는 mark-sweep-compact 3단계 algorithm을 사용하여 수행된다.
1. Marking
scavenger와 같은 느낌으로 garbage collector가 사용중인 object와 사용중이지 않은 object를 식별한다. 이는 reference가 stack에서 heap방향으로 이루어지기에 stack pointer에서부터 시작해 이루어진다. heap momory를 방향성 graph로 간주하여 DFS를 통해 reference여부를 판단하고, 사용중인 object일시에 marking해둔다.
2. Sweeping
heap을 탐색한 이후에 살아있는 것으로 marking되지 않은 모든 object의 memory address를 기록한다. 해당 단계에서는 free되는 것이 아닌 단순히 "기록"이 이루어지는 것이다. 기록되는 것은 free-list에 있으며 이후에 참조하여 재사용된다.
3. Compacting
fragmentation을 줄이는 단계이다. 필요할 시에 sweeping이후 모든 살아있는 object들이 함께 이동한다. 이 과정을 통해 fragmentation을 감소시키고, 새 object에 memory를 할당하는 성능이 향상된다.
major gc역시 stop-the-world process이지만, scavenger와 달리 무거운 과정이므로 아래의 기술들을 사용한다.(아마 몇년된 기술 내용이라 현재에는 바뀐 듯 하다)