자바스크립트에서 메모리를 관리하는 가비지 컬렉터가 소스 코드가 실행하기 위한 환경을 제공하는 실행 컨텍스트와 관련하여 어떻게 동작하는지 알아보자.
가비지 컬렉터는 애플리케이션이 할당한 메모리 공간을 주기적으로 검사하여 더 이상 사용되지 않는 메모리를 해제하는 기능이다.
아래 예제로 가비지 컬렉터의 동작을 간단히 알아보자.
// 1. 변수 선언
var a;
// 2. 변수 할당
a = 1;
// 3. 변수 재할당
a = 10;
자바스크립트 엔진은 변수 a 선언을 먼저 실행하여 a가 저장할 메모리 공간을 확보한다.
이때, var 키워드로 선언한 변수에는 undefined로 초기화된다. 따라서 식별자 a는 undefined가 저장된 메모리 공간을 가리키게 된다.
이후 변수에 원시값 1을 할당하게 되면, 자바스크립트 엔진이 이전에 메모리 공간에 저장되어 있는 undefined를 수정하는 것이 아닌, 새로운 메모리 공간을 확보하여 그곳에 원시값 1을 저장한다. 따라서 식별자 a는 1이 저장된 메모리 공간을 가리킨다.
그 이유는 원시값은 변경 불가능한 값이기 때문에, 한번 생성되면 그 값을 변경할 수 없다. 따라서 이전의 값을 수정하지 않고 새로운 원시 값을 저장할 메모리 공간을 확보하는 것이다.
다음으로 변수를 재할당하는 것도 마찬가지로 새로운 메모리 공간을 확보하여 원시값 10을 저장한다. 즉 식별자 a는 10이 저장된 메모리 공간을 가리킨다.
이때 기존에 사용했던 메모리 공간은 더이상 어떤 식별자도 참조하지 않는 공간이 된다. 그런 메모리 공간은 가비지 컬렉터에 의해 해제되어 가용 메모리 풀(사용 가능한 메모리)에 반환된다.
하지만 위와 같은 간단한 방법으로 동작하지 않는다...
C언어 같은 타 언어에서는 메모리 공간을 스택과 힙 영역으로 나눠서 관리한다.
스택(stack)은 지역 변수와 매개 변수가 저장되는 공간으로, 저장할 데이터의 크기가 정해져있는 경우에 스택 공간에 저장한다.
힙(heap)은 사용자가 직접 관리해야 하는 메모리 영역으로, 사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다.
이렇게 사용자가 직접 메모리를 관리할 수 있는 기능을 제공하는 언어를 매니지드 언어(managed language)라고 부른다. 개발자가 명시적으로 메모리를 할당하고 해제할 수 있는 mallco()
, free()
같은 저수준 메모리 제어 기능을 제공한다.
스택 영역은 저장할 데이터 크기가 정해져있는 경우에 저장하는 공간으로, 연속적인 메모리 블록을 할당받는다. 그리고 따로 명시적으로 메모리 공간을 해제할 필요가 없다. 자동으로 메모리 공간이 해제되기 때문이다. 하지만 힙 영역은 동적으로 메모리를 할당하고 직접 해제해주어야 한다.
자바스크립트에서는 이렇게 개발자가 동적으로 메모리를 할당하고 해제하는 기능을 제공하지 않는다.
또한 자바스크립트는 타입이 따로 정해저 있지 않는 동적 타입 언어이기 때문에, 스택 영역처럼 크기가 정해져 있는 데이터를 저장하는 메모리 영역을 사용하지도 않는다.
즉 자바스크립트는 스택과 힙이 아닌, 다른 방식으로 메모리 관리를 수행한다.
위의 예제에서 보여주었던 그림처럼, 직접 메모리 셀에 접근해 값을 저장하지도 않는다.
자바스크립트에서 메모리 공간을 확보하고 관리하는 방식은 바로 실행 컨텍스트이다.
그럼 이제 실행 컨텍스트와 관련하여 가비지 컬렉터가 어떤 방식으로 메모리를 해제하는지 알아보자.
실행 컨텍스트는, 자바스크립트의 소스 코드를 실행하는데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다.
즉, 실행 컨텍스트는 스코프, 식별자, 코드 실행순서를 관리한다.
위와 같이 소스코드를 실행하고 관리하기 위해서 자바스크립트 엔진은 소스코드를 2가지 단계로 나누어 처리한다.
소스코드 평가 과정에서는 실행 컨텍스트를 생성한다.
변수, 함수 등의 선언문만 먼저 실행하여 생성된 식별자를 키로 실행 컨텍스트가 관리하는 스코프에 등록한다.
소스코드 평가가 종료되면, 선언문을 제외한 소스코드가 순차적으로 실행된다. 이를 런타임이라고 말한다. 소스코드에 필요한 정보 (변수, 함수 참조)를 실행 컨텍스트가 관리하는 스코프에서 검색하여 취득하고, 값의 변경 등 실행 결과를 다시 실행 컨텍스트가 관리하는 스코프에 등록한다.
그럼, 소스코드의 순서는 어떻게 관리하는 것일까?
바로 실행 컨텍스트 스택이다.
그렇다면, 실행 컨텍스트가 식별자를 어디에 저장하고 관리하는 것일까?
바로 실행 컨텍스트의 렉시컬 환경이다.
식별자에 바인딩된 값은 환경 레코드에서, 상위 스코프에 대한 참조는 외부 렉시컬 환경 참조에서 관리한다.
그렇다면 다음 예제를 실행 컨텍스트와 그의 렉시컬 환경으로 살펴보도록 하자.
var x = 1;
const y = 2;
function foo (a) {
var x = 3;
const y = 4;
}
foo(20);
위 코드에서 foo 함수가 호출된 다음 foo 함수 코드 실행 단계에서 실행 컨텍스트 스택은 다음과 같다.
foo함수의 실행이 끝나게 되면, 실행 컨텍스트 스택에서 함수 실행 컨텍스트가 pop되어 제거된다.
그렇게 되면, foo 함수의 렉시컬 환경을 참조하고 있는 것은 아무것도 없게 된다. 이렇게 어떤 식별자도 참조하지 않고 서로를 참조하고 있는 것을 외딴섬이라고 부른다.
가비지 컬렉터는 아래처럼 어떤 식별자도 참조하고 있지 않는 외딴섬을 수거함으로서 메모리를 동적으로 해제한다.
이처럼 자바스크립트는 실행 컨텍스트와 가비지 컬렉터의 조합으로 메모리 공간을 확보하고 해제하며 소스 코드의 실행 환경을 조성하고, 그 결과를 저장하게 된다.