Introduce
C와 같은 low-level 언어는 malloc()과 free()와 같은 함수로 수동 메모리 관리가 가능하다. 반면, JavaScript는 객체가 생성될 때 자동으로 메모리를 할당하고 더 이상 사용되지 않을 때 garbage collection를 통해 이를 해제한다. 이러한 자동화는 개발자에게 메모리 관리를 신경 쓰지 않아도 된다는 잘못된 인식을 줄 수 있어 혼란의 원인이 될 수 있다. 이 글에서는 JavaScript에서 메모리 관리가 어떻게 이루어지는지 알아보아 효율적인 메모리 관리를 가능하게 하는데 도움이 되고자 한다.
Prelimiares
Javascript data type
자바스크립트에는 기본형, 참조형 자료형이 있다. 기본형과 참조형의 종류들은 아래 그림에 나타내었다.
기본형과 참조형의 차이점은 기본형과 참조형 할당할 때 모두 복제되기 하지만 기본형은 값이 담긴 주소값이 바로 복제되고 참조형은 값이 담긴 주소값을 가르키는 주솟값을 복제한다. 또한 기본형은 불변성을 띈다. let, var선언자와 함께 기본형 자료형에 값을 여러번 바꾼적이 있을텐데 불변성이 무슨말일까 할 수도 있다. 이를 이해하기 위해선 기본형 자료형이 메모리상에 저장되는 것을 이해할 필요가 있다.
기본형 자료형은 메모리에 저장될 때 식별자와 실제 데이터는 서로 다른 메모리에 저장된다. 예를 들어 식별자는 1003번 메모리 주소에 실제 데이터가 저장되어 있는 메모리 주소와 함께 저장된다. 이때 기본형 자료에 새로운 값을 할당하면 데이터가 저장되어 있는 메모리의 값을 바꾸는 것이 아니라 새로운 값을 메모리 상에서 찾고, 그 값이 없으면 그 값을 메모리 상에 새로 할당해서 식별자가 저장되어 있는 메모리의 참조 주소를 바꾸기 때문에 기본형 자료형은 불변성을 띈다고 한다. 불변성과 상수를 혼동하기도 쉬운데 변수와 상수를 구분 짓는 방법은 식별자가 저장되어 있는 메모리의 변경여부에 따라 구분되고, 가변성과 불변성은 실제 데이터가 저장되어 있는 메모리의 변경여부에 따라 구분된다.
JavaScript engines data store structures
- Memory Stack: static data를 저장하는 데 사용되는 data structure다. Static data는 컴파일 하는 동안 Javascript 엔진에 의해 크기가 정해진 data를 의미한다. JavaScript에서 static data는 string, number, boolean, null, undefined와 같은 primitive values를 포함한다. Object와 function을 가리키는 reference도 포함된다. 이때 함수의 실행 컨텍스트가 저장되는 Call stack과 구별하기 위해 이 글에선 Memory stack이라 칭한다. Memory Stack엔 함수에 대한 참조만 저장된다. Static data를 위해 고정된 양의 메모리가 할당된다. 이 과정을 static memory allocation이라고 한다.
- Heap: 힙은 JavaScript에서 object와 function을 저장하는 데 사용된다. 이때 함수는 함수 객체 그 자체이다. Javascript에서는 모든 것이 객체인 것을 알 것이다. 함수도 객체이다. 또한 Heap에는 고정된 양의 메모리를 할당하지 않는다. 대신 필요한 만큼 더 많은 space를 할당한다.
Memory Cycle
프로그래밍 언어와 상관없이, Memory Cycle은 거의 동일하다:
- 필요한 메모리를 할당한다
- 할당된 메모리를 사용한다 (읽기, 쓰기)
- 더 이상 필요하지 않은 할당된 메모리를 해제한다
두 번째 부분은 모든 언어에서 명시적이다. 첫 번째와 세 번째 부분은 low-level 언어에서는 명시적이지만, JavaScript와 같은 고수준 언어에서는 대부분 암시적이다. 명시적이라는 것은 코드에서 직접적으로, 분명하게 어떤 동작이나 명령을 나타내는 것을 의미한다. 암시적인 것은 언어나 런타임 환경이 자동으로 처리하는 것을 의미한다.
Main
JavaScript는 garbage collection라는 자동 메모리 관리 방식을 사용한다. garbage collector의 목적은 메모리 할당을 모니터링하고 할당된 메모리 블록이 더 이상 필요하지 않게 되었을 때 이를 해제하는 것이다. 이 과정은 완벽히 메모리 관리를 관리할 순 없는데 왜냐하면 특정 메모리가 여전히 필요한지 여부를 결정하는 일반적인 문제는 해결할 수 없기 때문이다.
이에 모든 현대 JavaScript 엔진은 mark-and-sweep garbage collector를 사용하고 있다. 이 알고리즘을 통해 circular reference와 같은 문제를 방지할 수 있다. mark-and-sweep 알고리즘은 더 이상 필요하지 않은 객체를 접근할 수 없는 객체로 정의하여 문제를 해결한다.
이 알고리즘은 roots라고 불리는 객체 집합의 지식을 가정한다. JavaScript에서는 전역 객체가 root다. 주기적으로 garbage collector는 이러한 roots에서 시작해, roots에서 참조하는 모든 객체를 찾고, 그 객체들에서 참조하는 다른 모든 객체를 찾는 과정을 반복한다. 이렇게 해서 garbage collector는 모든 도달 가능한 객체를 찾고, 도달할 수 없는 모든 객체를 수집한다.
이 알고리즘은 이전의 알고리즘에 비해 개선된 점이 있는데, 참조가 0인 객체는 실제로 접근할 수 없다는 점을 고려한다는 것이다. 하지만, 여전히 garbage collection을 수동으로 제어할 수 없는 한계가 있다.
Reference
- 정재남. (2019). 코어 자바스크립트. 위키북스.
- Medium. (2024, July). Memory life cycle: Heap, Stack, Call Stack, String Pool. Retrieved from Medium
- Mozilla Developer Network (MDN). (2024, July). Memory management. Retrieved from MDN Web Docs
'JS' 카테고리의 다른 글
[JS] Javascript에서의 모듈 시스템 (1) | 2024.07.23 |
---|---|
[JS] Javascript에서의 OOP (0) | 2024.07.22 |
[JS] Javascript의 프로토타입과 상속: 개념과 동작 이해 (0) | 2024.01.12 |
[JS] Javascript에서의 Closures (1) | 2024.01.11 |
[JS] Javascript에서의 this (1) | 2024.01.09 |