프로그래밍 언어마다 문자열을 저장하는 자료형이 다 다르므로, 이 자료형이 차지하고 있는 바이트를 이해할 수 있어야 한다
유니코드(Unicode)는 유니코드 협회(Unicode Consortium)가 제정하는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다
이 표준에는 ISO 10646 문자 집합, 문자 인코딩, 문자 정보 데이터베이스, 문자를 다루기 위한 알고리즘 등을 포함하고 있다
인코딩이란 어떤 문자나 기호를 컴퓨터가 이용할 수 있는 신호로 만드는 것이다
이 신호를 입력하는 인코딩
과
문자를 해독하는 디코딩
을 하기 위해서는 미리 정해진 기준을 바탕으로 입력과 해독이 처리되어야 한다
이렇게 인코딩과 디코딩의 기준을 문자열 세트 또는 문자셋(charset) 이라고 한다
이 문자셋의 국제 표준이 유니코드다
영문 알파벳을 사용하는 대표적인 문자 인코딩으로 7 비트로 모든 영어 알파벳을 표현할 수 있다
52개의 영문 알파벳 대소문자와, 10개의 숫자, 32개의 특수 문자, 그리고 하나의 공백 문자를 포함한다
유니코드는 ASCII를 확장한 형태다
UTF-8과 UTF-16은 인코딩 방식의 차이를 의미한다
UTF-8은 Universal Coded Character Set + Transformation Format – 8-bit의 약자로, UTF- 뒤에 등장하는 숫자는 비트(bit)다
1. UTF-8 특징: 가변 길이 인코딩
UTF-8은 유니코드 한 문자를 나타내기 위해 1 byte(= 8 bits) 에서 4 bytes까지 사용한다
원리
예를 들어, 코 라는 문자의 유니코드는 U+CF54 (16진수, HEX)로 표현된다
이 문자를 이진법(binary number)으로 표시하면, 1100-1111-0101-0100 이 된다
이 문자를 UTF-8로 표현하면, 다음과 같이 3byte 의 결과로 표현된다
UTF-8로 표현된 '코'
1110xxxx 10xxxxxx 10xxxxxx # x 안에 순서대로 값을 채워넣습니다.
11101100 10111101 10010100
'코'라는 문자를 UTF-8로 표현할 수 있다
let encoder = new TextEncoder(); // 기본 인코딩은 'utf-8' encoder.encode('코') // Uint8Array(3) [236, 189, 148] (236).toString(2) // "11101100" (189).toString(2) // "10111101" (148).toString(2) // "10010100"
ASCII 코드는 7비트로 표현되고, UTF-8 에서는 다음과 같이 1 byte의 결과로 만들 수 있다
다음 예제는 b 라는 문자를 UTF-8로 인코딩한 결과다
UTF-8로 표현된 'b'
0xxxxxxx
01100010
'b'라는 문자를 UTF-8로 표현할 수 있다
encoder.encode('b') // Uint8Array [98] (98).toString(2) // "1100010"
이처럼, UTF-8은 1 byte에서 4 bytes까지의 가변 길이를 가지는 인코딩 방식이다
네트워크를 통해 전송되는 텍스트는 주로 UTF-8로 인코딩된다
사용된 문자에 따라 더 작은 크기의 문자열을 표현할 수 있기 때문이다 ASCII 문자는 1 바이트만으로 표현 가능한 것처럼 말이다
UTF-8은 ASCII 코드의 경우
1 byte
, 크게 영어 외 글자는2byte
,3byte
, 보조 글자는4byte
를 차지한다
이모지는 보조 글자에 해당하기 때문에4byte
가 필요하다
2. UTF-8 특징: 바이트 순서가 고정됨
UTF-16에 비해 바이트 순서를 따지지 않고, 순서가 정해져 있다
3. UTF-16 특징: 코드 그대로 바이트로 표현 가능, 바이트 순서가 다양함
UTF-16은 유니코드 코드 대부분(U+0000부터 U+FFFF; BMP) 을 16 bits로 표현한다
부분에 속하지 않는 기타문자는 32 bit(4 bytes)로 표현하므로 UTF-16도 가변 길이라고 할 수 있으나, 대부분은 2 바이트로 표현한다
U+ABCD라는 16진수를 있는 그대로 이진법으로 변환하면 1010-1011-1100-1101 이다
이 이진법으로 표현된 문자를 16 bits(2 bytes)로 그대로 사용하며, 바이트 순서(엔디언)에 따라 UTF-16의 종류도 달라진다
UTF-8에서는 한글은 3 바이트
, UTF-16에서는 2 바이트
를 차지한다
UTF-8에서는 영문은 1 바이트
, UTF-16에서는 2 바이트
를 차지한다
컴퓨터나 스마트폰의 기기 그 자체(하드웨어)는 스스로 할 수 있는 일이 없다
하드웨어의 설계를 바탕으로 하드웨어에게 일을 시켜야만 그 의미가 있다
하드웨어에게 일을 시키는 주체가 바로 운영체제이다
운영체제가 없다면, 응용 프로그램이 실행될 수 없다
응용 프로그램은 컴퓨터를 이용해 다양한 작업을 하는 것이 목적인고, 운영체제는 응용 프로그램이 하드웨어에게 일을 시킬 수 있도록 도와준다
하드웨어를 구성하는 일을 하는 CPU, 자료를 저장하는 RAM, 디스크 등의 시스템 자원을 관리하는 주체가 바로 운영체제이다
모든 응용 프로그램이 시스템의 자원을 마음대로 사용한다면, 해커에 의한 공격에 무방비한 상태가 된다
악의적인 목적을 가진 프로그램이 디스크의 모든 민감한 정보에 접근하거나, 내 스마트폰의 특정 앱이 카메라를 아무때나 실행해서 촬영한다고 생각하면 끔찍하다
따라서, 응용 프로그램은 권한에 대한 관리가 필요하다 또한 여러 사람이 하나의 기기를 사용하는 경우에는 사용자를 관리하는 일도 매우 중요하다
응용 프로그램이 실행되고, 시스템 자원을 사용할 수 있도록 권한과 사용자를 관리한다
응용 프로그램이 운영체제를 통해 컴퓨터에게 일을 시키려면, 컴퓨터를 조작할 수 있는 권한을 운영체제로부터 부여받아야 한다
권한을 부여받고 난 후에는, 운영체제가 제공하는 기능을 이용할 수 있다
응용 프로그램이 운영체제와 소통하기 위해서는, 운영체제가 응용 프로그램을 위해 인터페이스(API)를 제공해야 한다
응용 프로그램이 시스템 자원을 사용할 수 있도록, 운영체제 차원에서 다양한 함수를 제공하는 것을 시스템 콜(System call)이라고 부른다
ex)
스마트폰에서 사용자에게 어떤 디바이스(카메라 등)의 사용을 허락받는 화면을 본 적이 있을 것이다
이와 마찬가지로, 응용 프로그램 역시 운영체제가 프린터 사용을 허가해주지 않는다면 사용할 수 없다 워드프로세서 프로그램이 프린터를 사용해서 인쇄하기 위해서는, 워드프로세서 프로그램은 운영체제로부터 프린터 사용에 대한 권한을 부여받아야 한다
응용 프로그램이 프린터 사용에 대한 권한을 획득한 후에는, 프린터를 사용할 때 필요한 API를 호출해야 한다, 이 API는 시스템 콜로 이루어져 있다
운영체제에서는 실행 중인 하나의 애플리케이션을 프로세스라고 부른다
사용자가 애플리케이션을 실행하면, 운영체제로부터 실행에 필요한 메모리를 할당 받아 애플리케이션의 코드를 실행한다
이때 실행되는 애플리케이션을 프로세스라고 부른다, 예를 들어 Chrome 브라우저를 두 개 실행하면, 두 개의 프로세스가 생성된다
이렇게 하나의 애플리케이션은 여러 프로세스(다중 프로세스)를 만들기도 한다
하기의 사진에서 확인할 수 있는 항목 하나하나가 전부 프로세스이다
스레드는 사전적 의미로 한 가닥의 실이라는 뜻이다
한 가지 작업을 실행하기 위해 순차적으로 실행한 코드를 실처럼 이어 놓았다고 해서 유래된 이름이다
하나의 스레드는 코드가 실행되는 하나의 흐름이기 때문에, 한 프로세스 내에 스레드가 두 개라면 코드가 실행되는 흐름이 두 개 생긴다는 의미다
멀티 태스킹은 두 가지 이상의 작업을 동시에 처리하는 것을 의미한다
운영체제는 멀티 태스킹을 할 수 있도록, 프로세스마다 CPU 및 메모리 자원을 적절히 할당하고 병렬로 실행한다
예를 들어 워드로 문서작업을 하면서, 동시에 Chrome 브라우저에서 음악을 들을 수 있다
물론 멀티 태스킹은 꼭 멀티 프로세스를 의미하는 것은 아니며, 하나의 프로세스 내에서 멀티 태스킹을 할 수 있도록 만들어진 애플리케이션도 있다
멀티 프로세스
가 애플리케이션 단위의 멀티 태스킹이라면, 멀티 스레드
는 애플리케이션 내부에서의 멀티 태스킹이라고 할 수 있다
멀티 스레드는 다양한 곳에서 사용되며, 대용량 데이터의 처리시간을 줄이기 위해 데이터를 분할하여 병렬로 처리하는 데에 사용할 수도 있고, UI를 가지고 있는 애플리케이션에서 네트워크 통신을 하기 위해 사용할 수도 있다
그리고 여러 클라이언트의 요청을 처리하는 서버를 개발할 때에도 사용된다
프로세스를 이용하여 동시에 처리하던 일을 스레드로 구현할 경우, 메모리 공간과 시스템 자원의 소모가 줄어든다
스레드 간의 통신이 필요한 경우
에도 별도의 자원을 이용하는 것이 아니라, 전역 변수의 공간 또는 동적으로 할당된 공간인 Heap 영역을 이용한다 따라서, 프로세스 간 통신 방법(IPC)에 비해 스레드 간의 통신 방법이 훨씬 간단하다 시스템의 처리량(Throughput)이 향상되고 자원 소모가 줄어들어 자연스럽게 프로그램의 응답 시간이 단축된다
이런 장점 때문에 여러 프로세스로 할 수 있는 작업을 하나의 프로세스에서 스레드로 나눠 수행한다
멀티 프로세스 기반으로 프로그래밍할 때에는 프로세스 간 공유하는 자원이 없다
따라서 동일한 자원에 동시에 접근하는 일이 없었지만, 멀티 스레딩을 기반으로 프로그래밍할 때에는 공유하는 자원에 대하여 고민이 필요하다
서로 다른 스레드가 같은 데이터에 접근하고, 힙 영역을 공유하기 때문에 서로 다른 스레드가 서로 사용중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽어오거나 수정하는 일이 발생할 수 있다
그렇기 때문에 멀티스레딩 환경에서는 동기화 작업이 필요하다
동기화를 통해 작업 처리 순서를 제어하고, 공유 자원에 대한 접근을 제어해야 한다
데드락 (Deadlock, 교착 상태)
운영체제에서 데드락(교착상태)이란, 시스템 자원에 대한 요구가 뒤엉킨 상태다
즉, 둘 이상의 프로세스가 다른 프로세스가 점유하고 있는 자원을 서로 기다릴 때 무한 대기에 빠지는 상황을 일는다
데드락(Deadlock)의 발생조건
데드락이 발생하기 위한 조건은 크게 4가지로 말할 수 있다
상호 배제
한 번에 프로세스 하나만 해당 자원을 사용할 수 있다
사용 중인 자원을 다른 프로세스가 사용하려면 요청한 자원이 해제될 때까지 기다려야 한다.점유 대기
자원을 최소한 하나 보유하고, 다른 프로세스에 할당된 자원을 점유하기 위해 대기하는 프로세스가 존재해야 한다비선점
이미 할당된 자원을 강제로 빼앗을 수 없다(비선점)순환 대기
대기 프로세스의 집합이 순환 형태로 자원을 대기하고 있어야 한다
뮤텍스(Mutex), 세마포어(Semaphore)
뮤텍스
한 쓰레드, 프로세스에 의해 소유될 수 있는 Key를 기반으로 한 상호배제기법
세마포어
Signaling mechanism. 현재 공유자원에 접근할 수 있는 쓰레드, 프로세스의 수를 나타내는 값을 두어 상호배제를 달성하는 기법
동시에 돌릴 수 있는 스레드 수는 컴퓨터에 있는 코어 갯수로 제한된다
운영체제(또는 가상 머신)는 각 스레드를 시간에 따라 분할하여, 여러 스레드가 일정 시간마다 돌아가면서 실행되도록 한다, 이런 방식을 시분할이라고 한다
Concurrency(동시성, 병행성): 여러 개의 스레드가 시분할 방식으로 동시에 수행되는 것처럼 착각을 불러일으킴
Parallelism(병렬성): 멀티 코어 환경에서 여러 개의 스레드가 실제로 동시에 수행됨
다른 태스크(프로세스, 스레드)가 시작할 수 있도록 이미 실행중인 태스크(프로세스, 스레드)를 멈추는 것을 Context Switching이라고 한다
가비지 컬렉션은 프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것이며, 이 기능을 가진 언어(혹은 엔진)는 자바, C#, 자바스크립트 등이 있다
한 객체에 flag
를 두고, 가비지 컬렉션 사이클마다 flag에 표시 후 삭제하는 mark and sweep 방법이다
한 객체를 참조
하는 변수의 수
를 추적하는 방법이다
크롬 브라우저 및 node.js의 v8 엔진은, 어떻게 가비지 컬렉팅을 하고 있나?
자바스크립트는 인터프리터 언어기 때문에 코드를 해석하고 실행하는 엔진이 필요하다
V8 엔진은 자바스크립트를 해석하고 컴파일하여 기계어로 변환한다
V8은 C++로 작성되었으며 모든 C++ 애플리케이션에서 내장할 수 있다V8 메모리 구조
자바스크립트가 단일 스레드이기 때문에 V8은 자바스크립트 컨텍스트 당 한 개의 프로세스를 사용한다
따라서 만약 서비스 워커를 사용한다면 워커 당 한 개의 새로운 V8 프로세스를 생성하게 될 것이다
실행 중인 프로그램은 V8 프로세스에서 할당된 일정량의 메모리로 표현되고 이를Resident Set
이라고 한다
JVM 메모리 구조와 유사하다
힙 메모리
V8 엔진은 힙 메모리에 객체나 동적 데이터를 저장한다
힙 메모리는 메모리 영역에서 가장 큰 블록이면서 가비지 컬렉션(GC)이 발생하는 곳이다
힙 메모리 전체에서 가비지 컬렉션이 실행되는 것은 아니며,Young
과Old
영역에서만 실행된다
- New 영역: New 영역 또는 "Young 제너레이션"은 새로 만들어진 모든 객체를 저장하고 이 객체들은 짧은 생명 주기를 가진다
이 영역은 크기가 작고 JVM에서 S0와 S1과 같은 2개의 세미(semi) 영역을 가진다
이 영역은 이후에 살펴볼 스캐벤져(Scavenger, 마이너 GC)가 관리한다
New 영역의 크기는 --min_semi_space_size(초기값)와 --max_semi_space_size(최대값) V8 엔진의 플래그 값을 사용해 조정할 수 있다
- Old 영역: Old 영역 또는 "Old 제너레이션"은 마이너 GC가 두 번 발생할 동안 "New 영역"에서 살아남은 객체들이 이동하는 영역이다
이 영역은 메이저 GC(Mark-Sweep 및 Mark-Compact)가 관리한다
Old 영역의 크기는 V8 엔진의 플래그 값--initial_old_space_size(초기값)
와max_old_space_size(최대값)
을 사용해 조정할 수 있다
이 영역은 다시 2개의 영역으로 나누어진다
- Old 포인터 영역: 살아남은 객체들을 가지며, 이 객체들은 다른 객체를 참조한다
- Old 데이터 영역: 데이터만 가진 객체들(다른 객체를 참조하지 않는다)을 가진다
문자열, 박싱(boxing)된 숫자, 실수형(double)로 언박싱(unboxing)된 배열은 마이너 GC가 두 번 발생하면서 "New 영역"에서 살아남아 이 영역으로 이동한다
- 라지 오브젝트 영역: 다른 영역의 제한된 크기보다 큰 객체들이 살고 있는 영역이다
각 객체는 자체 mmap 메모리 영역을 갖는다
라지 오브젝트들은 가비지 컬렉터로 이동하지 않는다
- 코드 영역: 실시간(JIT) 컴파일러가 컴파일된 코드들을 저장하는 곳이다
유일하게 실행 가능한 메모리가 있는 영역이다
(코드들은 "라지 오브젝트 영역"에 할당될 수도 있고 실행도 가능하다)
- 셀 영역, 속성 셀 영역, 맵 영역: 이 영역들은 각각 Cells, PropertyCells, Maps을 포함한다
각 영역은 모두 같은 크기의 객체들을 포함하며, 어떤 종류의 객체를 참조하는지에 대한 제약이 있어서 수집을 단순하게 만든다각 영역은 페이지들로 구성되어 있다
페이지는 운영 체제에서 mmap(또는 Windows에서 MapViewOfFile)로 할당된 연속된 메모리 청크를 의미한다
각 페이지 크기는 라지 오브젝트 영역을 제외하고 1MB를 차지한다스텍
스택은 메모리 영역이고 V8 프로세스마다 하나의 스택을 가진다
스택은 메서드와 함수 프레임, 원시 값, 객체 포인터를 포함한 정적 데이터가 저장되는 곳이다
스택 메모리의 크기 제한은 --stack_size V8 플래그 값을 사용해 설정할 수 있다
- 전역 스코프는 스택에서 "전역 프레임"에 보관된다
- 모든 함수 호출은 프레임 블록으로 스택 메모리에 추가된다
- 반환 값과 인자를 포함한 모든 지역 변수들은 스택에서 함수 프레임 블록 안에 저장된다
- int와 string과 같은 모든 원시 타입 값은 스택에 바로 저장된다. 이는 전역 스코프에서도 - 적용되며, 자바스크립트에서 문자열은 원시 타입에 해당한다
- Employee와 Function과 같은 객체 타입의 값은 힙에서 생성되고 스택 포인터를 사용해 힙에서 스택을 참조한다. 함수들은 자바스크립트에서 객체이다. 전역 스코프에도 적용된다
- 현재 함수에서 호출된 함수들은 스택의 최상단에 추가된다
- 함수 프레임이 반환(역자주: 함수가 종료)될 때 스택에서 제거된다
- 주요 프로세스가 완료될 때 힙에 있는 객체들은 어떤 포인터도 가지고 있지 않고 혼자 남게 된다
- 명시적으로 복사하지 않으면, 다른 객체 내의 모든 객체 참조들은 참조 포인터를 사용해 연결된다
스택
은 자동으로 관리되고 V8 엔진 자체가 아닌 운영 체제가 수행하므로 걱정하지 않아도 된다
힙
은 운영 체제에 의해 자동으로 관리되지 않고 가장 큰 메모리 영역과 동적 데이터를 보유하고 있기 때문에, 시간이 지남에 따라 프로그램의 메모리가 기하급수적으로 증가할 수 있다
또한 시간이 지나면서 조각화되어 애플리케이션 속도를 느리게 만든다힙에서 포인터와 데이터를 구분하는 것은 가비지 컬렉션에서 중요하며 이를 처리하기 위해, V8 엔진은 "태그된 포인터(Tagged pointers)" 접근 방식을 사용한다
태그된 포인터
는 각 단어의 끝에 포인터 또는 데이터인지를 나타내는 비트 값을 저장하는 방식이다
이 접근 방식은 제한적인 컴파일러 지원이 필요하지만, 굉장히 효율적이면서 구현하기 쉽다V8 메모리 관리: 가비지 컬렉션
프로그램이 사용 가능한 것보다 더 많은 메모리를 힙에 할당하려고 할 때 메모리 부족 오류가 발생한다
힙이 잘못 관리되면 메모리 누수가 발생할 수 있다V8 엔진은 가비지 컬렉션을 사용해 힙 메모리를 관리한다
가비지 컬렉션
은 참조 없는 객체들이 사용하는 메모리를 비워서 새로운 객체를 생성하기 위한 공간을 만드는 역할을 한다
참조 없는 객체(orphan object)란, 스택으로부터 (다른 객체 내부의 참조를 통해) 더 이상 직접 혹은 간접적으로 참조되지 않는 객체를 말한다V8 엔진의 가비지 컬렉터의 역할은 V8 프로세스에서 재사용하기 위해 사용되지 않은 메모리를 회수하는 것이다
V8 가비지 컬렉터는 세대적이다(힙 메모리 내의 객체들은 수명에 따라 그룹화되고 다른 단계에서 제거된다)
마이너 GC (Scavenger)
마이너 GC는 New 영역(또는 Young 제너레이션)을 작고 깨끗하게 유지시킨다
객체들은 New 영역에 할당되고 크기가 매우 작다(상황에 따라 1~8MB를 차지한다)
"New 영역"에 대한 할당 비용은 매우 저렴하다
새 객체에 대한 공간을 예약하려고 할 때마다 증가하는 할당 포인터가 있기 때문이다
이 할당 포인터가 New 영역의 마지막에 도달하면, 마이너 GC가 발생한다
이 과정을 스캐벤저(Scavenger) 라고 하며, Cheney의 알고리즘을 사용해 구현되었다
스캐벤저는 매우 자주 발생하고 병렬 헬퍼 스레드를 사용하며, 굉장히 빠르다New 영역은 크기가 같은 2개의 세미 영역으로 나뉜다
To 영역과 From 영역이다
대부분의 할당은 To 영역에서 만들어진다(항상 Old 영역에서 할당된 실행 코드와 같은 특정 종류의 객체 제외)
To 영역이 가득 차면 마이너 GC가 발생한다
- 시작할 때 To 영역에 객체가 이미 있다고 가정해보자(01~06 블록은 사용된 메모리로 표시됨)
- 새 객체(07 블록)를 생성한다
- V8은 To 영역에서 필요한 메모리를 가져오려고 시도하지만, 객체들을 모두 수용할 수 없기 때문에 V8은 마이너 GC를 발생시킨다
- 마이너 GC는 객체들을 To 영역에서 From 영역으로 이동시킨다. 이제 모든 객체는 From 영역에 있고 To 영역은 비워진다
- 마이너 GC는 스택 포인터(GC 루트)부터 From 영역까지 객체 그래프를 재귀적으로 순회하면서 메모리를 사용한 객체들을 찾는다. 이 객체들은 To 영역의 페이지로 이동된다. 이 객체들을 참조하는 객체들은 To 영역의 페이지로 이동되고 포인터들은 갱신된다. From 영역의 모든 객체들을 찾을 때까지 이 과정이 반복된다. 마지막 객체까지 찾으면 To 영역은 자동으로 압축되어 조각화를 줄인다
- 이제 From 영역에 남아있는 객체는 가비지이므로 마이너 GC는 From 영역을 비운다
- 새 객체는 To 영역 메모리에 할당된다
- 어느 정도 시간이 지나 "To 영역"에 더 많은 객체가 생겼다고 가정해보자(07~09 블록은 사용된 메모리로 표시됨)
- 애플리케이션이 새 객체(10 블록)을 생성한다
- V8은 To 영역에서 필요한 메모리를 가져오려고 시도하지만, 객체들을 모두 수용할 수 없기 때문에 V8은 두 번째 마이너 GC를 발생시킨다
- 위 과정은 반복되고 두 번째 마이너 GC에서 생존한 객체들은 "Old 영역"으로 이동한다. 첫 번째 마이너 GC에서 생존한 객체들은 "To 영역"으로 이동하고 남아있는 객체들은 "From 영역"에서 제거된다
- 새 객체는 "To 영역"에 할당된다
마이너 GC는 stop-the-world 프로세스지만, 굉장히 빠르고 효율적이므로 무시할 수 있다
이 프로세스는 "New 영역"의 참조를 위해 "Old 영역"에서 객체들을 찾지 않기 때문에, "Old 영역"에서 "New 영역"까지 모든 포인터의 레지스터를 사용한다
이것은 Write barrier라고 하는 프로세스를 통해 저장 버퍼에 기록된다메이저 GC
메이저 GC
는 Old 제너레이션 영역을 작고 깨끗하게 유지시킨다
메이저 CG
는 V8에서 Old 영역의 메모리가 충분하지 않다고 판단될 때 발생한다
Old 영역
은 동적으로 계산된 크기에 기반하며, 마이너 GC 주기에서 채워진다
스캐벤저 알고리즘
은 작은 데이터 크기에는 적합하지만 Old 영역과 같이 큰 힙 메모리에는 적합하지 않다
메모리 오버헤드가 있기 때문에 메이저 GC는 Mark-Sweep-Compact 알고리즘을 사용하여 처리된다
메이저 GC는 Tri-color(흰색-회색-검은색) 마킹 시스템을 사용한다
따라서 메이저 GC는 세 단계의 프로세스를 거치며, 세 번째 단계는 조각화 휴리스틱(fragmentation heuristic)에 따라 실행된다
- 마킹(Marking): 두 알고리즘의 공통적인 첫 번째 단계로, 가비지 컬렉터가 어떤 객체가 사용중인지 식별한다
사용중이거나 GC 루트(스택 포인터)에 재귀적으로 도달할 수 있는 객체들은 활성 상태로 표시된다
마킹은 기술적으로 힙 메모리를 방향 그래프(directed graph)로 간주해 깊이 우선 탐색(depth first search)를 수행한다
- 스위핑(Sweeping): 가비지 컬렉터가 힙 메모리를 순회하면서 활성 상태로 표시되지 않은 객체들의 메모리 주소를 기록한다
이 공간은 이제 사용 가능한 목록(free-list)에서 사용 가능하다고 표시되며 다른 객체들을 저장하는 데 사용될 수 있다
- 압축(Compacting): 스위핑이 일어난 다음, 필요하다면 모든 활성 상태의 객체들이 함께 이동될 것이다
압축 단계는 조각화를 줄이고 새 객체들에 대한 메모리 할당 성능을 증가시킨다메이저 GC는 GC를 수행하는 동안 애플리케이션 실행을 멈추므로 stop-the-world GC라고도 한다
- 인크리멘탈 GC(Incremental GC): GC는 여러 개의 인크리멘탈 단계로 수행된다
- 동시 마킹(Concurrent marking): 마킹은 자바스크립트 메인 스레드에 영향을 주지 않고 다중 헬프 스레드를 사용해 동시에 수행된다
Write barrier는 헬퍼들이 동시에 마킹하는 동안 자바스크립트가 생성한 객체 간 참조를 추적하는 데 사용된다
- 동시 스위핑/압축(Concurrent sweeping/compacting): 스위핑과 압축은 자바스크립트 메인 스레드에 영향을 주지 않고 헬퍼 스레드에서 동시에 수행된다
- 레이지 스위핑(Lazy sweeping): 레이지 스위핑은 메모리가 필요할 때까지 페이지에서 가비지 삭제를 지연시킨다
메이저 GC 과정
- 많은 마이너 GC 주기를 거치고 Old 영역이 거의 다 찼으며 V8이 "메이저 GC"를 발생시킨다고 가정해보자
- 메이저 GC는 스택 포인터에서 시작해 재귀적으로 객체 그래프를 순회하면서, Old 영역 내 메모리를 사용한 객체와 남아있는 객체를 가비지로 표시한다
- 동시 마킹이 완료되거나 메모리 제한에 도달하면 GC는 메인 스레드를 사용하여 마킹의 마지막 단계를 수행한다, 이 때 일시 정지 시간이 발생한다
- 메이저 GC는 동시 스위프 스레드를 사용해 모든 참조 없는 객체들의 메모리를 사용 가능한 상태로 표시한다
또한 조각화를 피하기 위해 관련 메모리 블록을 동일한 페이지로 이동하도록 병렬 압축 작업도 발생한다
포인터들은 이 세 단계를 통해 갱신된다
많은 시간이나 연산이 필요한 작업의 결과를 저장해두는 것을 의미한다
컴퓨팅에서 캐시
는 일반적으로 일시적인(temporarily) 데이터를 저장하기 위한 목적으로 존재하는 고속의 데이터 저장공간이다
첫 작업 이후에 이 데이터에 대한 요청이 있을 경우, 데이터의 기본 저장공간에 접근할 때보다 더 빠르게 요청을 처리할 수 있다
캐싱을 사용하면 이전에 검색하거나 계산한 데이터를 효율적으로 재사용할 수 있다
캐시의 데이터
는 일반적으로 RAM(Random Access Memory) 과 같이 빠르게 액세스할 수 있는 하드웨어에 저장되며, 소프트웨어 구성 요소와 함께 사용될 수도 있다
캐시는 기본 스토리지 계층(SSD, HDD)에 액세스하여 데이터를 가져오는 더 느린 작업의 요구를 줄이고, 데이터 검색의 성능을 높인다
속도를 위해 용량을 절충하는 캐시는 일반적으로 데이터의 하위 집합을 일시적으로 저장한다
완전하고 영구적인 데이터가 있는 데이터베이스와는 대조적이다