메모리 누수(Memory Leak)는 부주의 또는 오류로 인해 더 이상 필요하지 않는 메모리를 해제하지 못하는 것을 의미한다. 한 가지 예시로 불필요한 var 키워드, this 키워드를 통해 전역 객체에 변수를 저장하는 것이 있다.
function saveGlobalVariable() {
this.memory = "leak";
}
saveGlobalVariable();
위 코드는 함수 내에서 this 키워드를 통해 전역 객체의 memory 프로퍼티에 'leak'
문자열을 저장하고 있다. 이렇게 되는 이유는 만약 new 키워드를 통해 인스턴스를 생성했다면 생성된 인스턴스의 memory 프로퍼티에 저장했겠지만 일반 함수로 호출했기 때문에 this는 전역 객체를 가리키게 되고 전역 객체에 변수를 저장하게되는 것이다.
이러한 상황에서 메모리 누수가 발생될 수 있고, 메모리 누수는 성능 저하로 이어질 수 있다. 그럼 메모리 관리는 어떻게 이루어질까?
C, C++ 같은 저수준의 언어는 수동 정리 메커니즘을 사용하는데 자바스크립트는 가비지 컬렉션(GC, Garbage Collection)이라는 자동 메모리 관리 방법을 사용한다. 가비지 컬렉션은 도달 가능성(reachability)이라는 개념을 사용해 메모리 관리를 수행한다. 여기서 도달 가능한(reachable) 값이라는 것은 참조에 의해 어떻게든 접근하거나 사용할 수 있는 값을 의미하고 도달 가능한 값은 메모리에서 해제되지 않는다.
그래서, 현재 함수의 지역 변수 | 매개변수, 중첩 함수의 체인에 있는 함수에서 사용되는 변수 | 매개변수, 전역 변수 등은 명백한 이유 없이 메모리가 해제되지 않는다.
더 이상 필요없는 객체를 다른 어떤 객체와도 참조되지 않는 객체라고 정의하여 메모리를 해제하는 방식이다. 다음 그림과 같이 참조되고 있는 객체들이 있다고 가정하자.
위 그림에서 참조되고 있지 않은 메모리는 없으므로 가비지 컬렉션에 의해 해제되는 메모리는 없다. 여기서 참조할 수 없는 메모리가 생긴다면 가비지 컬렉션에 의해 메모리가 해제된다.
위 그림에서 만약 빨간색 화살표로 연결된 참조를 해제한다고 가정해보자. 그럼 root에서부터 주황색 메모리까지 도달할 수는 없지만 주황색 메모리들은 서로를 참조하고 있는 순환 참조 구조를 형성한다.
Reference-counting 방법에서는 위 순환 참조 구조에 대해 메모리를 해제하지 않는다.
더 이상 필요없는 객체를 닿을 수 없는 객체(unreachable)라고 정의하여 메모리를 해제하는 방식이다. 이 방식은 root 객체(Javascript에서는 전역 변수)로부터 시작해서 닿을 수 있는 객체와 닿을 수 없는 객체로 나눈다.
이 방식은 3-1) Reference-counting보다 효율적이다. 위 그림에서 보듯 순환 참조 구조가 있다고 하더라도 root에서 부터 닿을 수 없는 객체이기 때문에 메모리에서 해제한다. 따라서 최신 브라우저에서는 가비지 콜렉션에서 Mark and Sweep 방식을 사용하고 있다.
자바스크립트의 GC는 자동으로 메모리를 관리하고 있어서 root에서부터 참조할 수 있는 데이터를 직접 삭제하고 싶어도 수동으로 해제할 수 없다.