1. 메모리 영역 분리의 근본적 이유: 정적 및 동적 할당의 효율화
자바스크립트 엔진은 메모리를 콜 스택(Call Stack)과 힙(Heap)으로 이원화하여 관리한다. 이는 데이터의 성격에 따라 최적의 관리 기법을 적용하기 위함이다.
- 콜 스택 (정적 할당 및 흐름 제어):
- 역할: 함수의 실행 순서를 기록하고 현재 실행 중인 맥락을 관리한다.
- 특징: 함수 호출 시 필요한 메모리 크기를 컴파일 단계(Pre-scan)에서 미리 계산할 수 있는 경우에 사용된다. LIFO(Last In, First Out) 구조를 통해 스택 포인터(SP)를 이동시키는 것만으로 매우 빠르게 메모리를 할당하고 해제한다.
- 힙 (동적 할당 및 데이터 저장):
- 역할: 크기가 고정되지 않은 객체, 배열, 동적 문자열 등을 보관하는 거대한 공유 저장소이다.
- 특징: 런타임에 데이터 크기가 변할 수 있어 메모리 크기를 예측할 수 없다. 빈 공간을 찾아 할당하는 과정이 스택보다 느리며, 사용되지 않는 데이터는 가비지 컬렉터(GC)가 복잡한 알고리즘을 통해 회수한다.
2. 콜 스택의 단위: 실행 컨텍스트(Execution Context)의 구조
함수가 호출될 때 콜 스택에 쌓이는 데이터의 단위는 실행 컨텍스트(EC)이다. 이는 단순히 변수 리스트가 아니라, 함수 실행에 필요한 모든 정보를 담은 객체 형태의 구조체이다.
- 렉시컬 환경(Lexical Environment):
- 환경 레코드(Environment Record): 현재 함수의 모든 지역 변수, 매개변수, 함수 선언이 식별자(이름)와 값의 매핑 형태로 저장되는 실질적인 변수 창고이다.
- 외부 환경 참조(Outer Environment Reference): 상위 스코프의 렉시컬 환경을 가리키는 포인터로, 이를 통해 스코프 체인이 형성된다.
- this 바인딩: 현재 실행 맥락에서의
this가 가리키는 객체의 주소를 저장한다.
- 제어 흐름의 처리: 조건문(
if)이나 반복문(for)은 데이터가 아닌 명령어(Instruction)이다. 이들은 실행 컨텍스트에 저장되는 것이 아니라 엔진이 바이트코드를 읽어 내려가는 과정에서 로직에 따라 실행 지점을 점프(Jump)시키며 처리된다.
3. 메모리 확보의 원리: 파싱과 스택 포인터(SP)
함수가 호출될 때 엔진은 '공간을 얼마나 확보할지'를 실행 전에 이미 알고 있다.
- 파싱 및 프리 스캔(Pre-scan): 엔진은 코드를 실행하기 전 전체 소스를 훑으며 함수 내부의 선언문들을 확인한다. 이 단계에서 필요한 슬롯(Slot)의 개수를 확정한다.
- 슬롯 단위 계산: 현대적인 64비트 엔진(V8 등)에서 하나의 변수 슬롯은 보통 8바이트 크기를 가진다. 만약 변수가 5개라면 40 바이트가 필요함을 계산한다.
- 스택 포인터 이동: 함수가 호출되는 순간, 엔진은 스택 포인터(SP)를 현재 위치에서 계산된 바이트 수만큼 단번에 위로 올린다. 이 동작만으로 해당 함수를 위한 전용 메모리 공간이 확보된다.
4. 재할당과 스택의 원칙
재할당 시 값이 바뀌는 것은 스택의 구조적 원칙을 해치지 않는다.
- 슬롯의 직접 수정:
var c = 2;에서 c = 3;으로 값이 바뀔 때, 엔진은 콜 스택 상단에 있는 현재 실행 컨텍스트의 환경 레코드를 찾아가 해당 c 슬롯의 비트 데이터를 덮어쓴다.
- 구조적 유지: 이는 스택에 새로운 데이터를 쌓거나 빼는 것이 아니라, 이미 확보된 공간 내부의 데이터를 수정하는 '쓰기' 작업이다. 따라서 함수 종료 시 스택 포인터를 아래로 내려 전체 공간을 반납하는 LIFO의 효율성은 그대로 유지된다.
5. 데이터 타입별 저장 방식 (V8 엔진 최적화)
모든 데이터가 주소값으로만 관리되는 것은 아니며, 효율을 위해 값 자체가 스택에 기록되기도 한다.
| 타입 | 저장 위치 | 상세 방식 |
|---|
| 작은 정수 (Smi) | 콜 스택 | 31비트 또는 32비트 범위의 정수는 주소가 아닌 값 자체를 슬롯에 직접 기록한다(최적화). |
| 불리언 (Boolean) | 콜 스택 | 슬롯 내부에 직접 값을 기록한다. |
| 참조형 (Object, Array) | 힙 (Heap) | 힙에 실제 객체를 생성하고, 콜 스택의 슬롯에는 그 메모리 주소(Pointer)를 기록한다. |
| 가변 데이터 (String, BigInt) | 힙 (Heap) | 크기를 예측할 수 없으므로 힙에 저장하고 스택에는 주소만 남긴다. |