프론트엔드/JavaScript

[JavaScript] JavaScript 엔진 구조 (Call Stack, Memory Heap)

JavaScript 엔진은 Call StackMemory Heap으로 구성되어 있습니다.

각각은 Stack, Heap 자료구조와 관련된 특징들을 보이고 있습니다.

그래서 저장하는 데이터나 동작하는 방식 등에서 차이가 나타납니다.

 

먼저 이 글을 통해 JavaScript의 엔진 구조에 대해 알아보겠습니다.

궁극적으로는 JavaScript의 Event Loop 방식을 이해하는 것을 목표로 합니다.


JavaScript 엔진 구조

JavaScript 엔진 구조

 

JavaScript 엔진은 Call StackMemory Heap으로 구성되어 있습니다.

대표적인 JavaScript 엔진의 예시로 구글의 V8 엔진이 있습니다.

 

또한 JavaScript는 한 번에 하나의 작업만 처리하는 싱글 스레드 언어입니다.

이는 하나의 Call Stack을 가졌다는 의미이기도 합니다.

 

대신 JavaScript는 이벤트 루프를 통해 동시성(Concurrency)을 지원합니다.

(이벤트 루프는 중요한 개념이므로 따로 포스팅으로 정리하겠습니다.)

 


Call Stack (호출 스택)

Call Stack에는 원시 타입 값과 실행 컨텍스트(Execution Context)가 저장됩니다.

 


Memory Heap (메모리 힙)

Memory Heap에는 참조 타입(객체, 배열, 함수.. )이 저장됩니다.

 

Heap은 동적으로 데이터를 할당할 수 있는 메모리 영역입니다.

그래서 크기를 예측하기 힘든 참조 타입을 저장하기에 적합한 구조가 됩니다.

 


데이터 저장 순서

JavaScript 엔진에 데이터가 저장된 모습

1. 원시 타입 데이터

  1. : 원시 타입 값 자체는 Call Stack에 저장합니다.
  2. 변수 : 변수는 값이 저장된 Call Stack 메모리의 주소를 참조합니다.

2. 참조 타입 데이터

  1. : 참조 타입(객체, 배열, 함수.. )은 Memory Heap에 저장합니다.
  2. Memory Heap 참조값 : 저장된 Memory Heap의 주소를 Call Stack에 저장합니다.
  3. 변수 : 변수는 Memory Heap 주소가 저장된 Call Stack 메모리의 주소를 참조합니다.

 


🔎 Garbage Collection

JavaScript는 값을 메모리에 저장하고 변수는 그 값의 주소를 참조하는 방식을 가집니다.

 

변수에 값을 재할당 한다면 변수가 참조하는 주소만 새로운 값으로 변경하면 됩니다.

(만약 그 값이 메모리에 없다면, 먼저 메모리에 새로운 값을 추가해야 합니다.)

 

그러다 보면 재할당 과정에서 더이상 참조되지 않는 값이 생길 수 있습니다.

이들은 JavaScript의 가비지 컬렉터에 의해 적절한 시점에 메모리에서 해제됩니다.

 

(가비지 컬렉션에 대해 더 궁금하시면 따로 찾아보시는 것을 추천합니다.) 

 


🔎 let과 const

let myArray = []; // BAD
const myArray = []; // GOOD!

"참조 타입(배열, 객체)들은 변경이 가능하니 let으로 선언하는 게 맞지 않나?"

이는 제가 과거에 잘 못 알고있던 개념 중 하나입니다.

 

결론부터 말하면, 참조 타입은 되도록이면 const로 선언하는 것이 좋습니다.

(참조 타입이 재할당 될 일이 없을 경우)

 

let은 값의 변경이 아닌 메모리 주소의 변경(재할당)이 가능함을 의미하기 때문입니다.

 

 

myArray에는 빈 배열의 주소값이 담김

 

메모리 상황을 함께 보면 더욱 이해하기 쉽습니다.

myArray 변수에는 빈 배열의 주소가 담겨 있습니다.

 

push, pop 등의 메서드를 통해 배열의 값을 변경하더라도 배열이 저장된 Heap의 주소 자체는 변하지 않습니다.

그러므로 참조 타입의 변경은 재할당과 상관이 없기 때문에 let이 반드시 필요하지 않습니다.