자바스크립트(JavaScript) 메모리 누수란?
자바스크립트는 가비지 컬렉션(Garbage Collection) 언어라고 불립니다. 즉, 변수를 선언하면 자동으로 메모리가 할당되고, 해당 변수를 참조하는 곳이 더 이상 없으면 할당된 메모리는 자동으로 해제됩니다. 그러나 실제 개발 환경에서는 이 메모리 해제 과정이 제대로 이루어지지 않아 메모리 누수(Memory Leak)가 발생하는 경우가 많습니다.
메모리 누수는 사용하지 않는 객체가 메모리에 계속 남아 있는 현상으로, 장기간 실행되는 웹 애플리케이션에서 성능 저하와 브라우저 충돌의 주요 원인이 됩니다. 아래에서 자바스크립트에서 가장 흔하게 발생하는 메모리 누수 사례를 살펴보겠습니다.
1) 의도치 않은 전역 변수(Accidental Global Variables)
선언되지 않은 변수를 참조하면 자바스크립트는 전역 객체(window)에 새로운 변수를 생성합니다. 다음 Example-1에서 languages 변수의 목적은 myArray 함수 내부에서만 사용하는 것이지만, var, let, const 키워드 없이 선언하면 전역 변수가 되어 버립니다.
Example-1
function myArray(arg) {
languages = "[javascript,.....]"; // window 객체에 생성됨
}더 큰 문제는 "this" 키워드를 잘못 사용할 때 발생합니다. 일반 함수 호출 시 this는 전역 객체를 가리키므로, Example-2처럼 작성하면 의도치 않게 전역 변수가 생성됩니다.
Example-2
function myArray(arg) {
this.languages = "[javascript,.....]"; // 전역 객체에 생성됨
}전역 변수의 스코프는 프로그램이 종료될 때까지 끝나지 않습니다. 따라서 페이지가 실행되는 동안 더 이상 필요하지 않게 되어도 메모리에 계속 남아 있게 됩니다. 이런 상황은 변수의 스코프가 끝나면 동적 메모리를 회수하는 가비지 컬렉터의 동작을 방해하여 메모리 누수를 유발합니다. 전역 변수가 많을수록 메모리 누수도 커집니다.
예방 팁: 'use strict' 모드를 사용하면 선언되지 않은 변수에 값을 할당할 때 오류가 발생하여 이러한 실수를 사전에 방지할 수 있습니다.
2) 클로저(Closure)
클로저는 외부 함수의 변수(스코프)에 접근할 수 있는 내부 함수를 말합니다. 특히 내부 함수는 외부 함수의 실행이 끝난 후에도 외부 함수의 스코프에 계속 접근할 수 있습니다. 문제는 내부 중첩 함수에서 실제로 참조하지 않는 변수까지 자동으로 접근 가능한 상태가 되어, 불필요하게 메모리에 계속 상주할 때 발생합니다.
아래 예제를 살펴보겠습니다. 클로저 내의 모든 내부 함수는 같은 컨텍스트(context)를 공유하므로, innFun()은 외부 함수 outFun이 반환하는 function() {}과 동일한 컨텍스트를 공유합니다. 3밀리초마다 outFun을 호출할 때마다 새로운 값이 전역 변수 newvalue에 할당됩니다. 이 function() {}을 가리키는 참조가 존재하는 한 공유 스코프가 유지되며, 내부 함수(innFun)의 일부인 배열도 내부 함수가 한 번도 호출되지 않았음에도 메모리에 계속 보관됩니다.
또한 외부 함수(outFun)를 호출할 때마다 이전의 function() {}이 새 함수의 변수 값으로 저장되므로, 이전에 공유하던 스코프 역시 계속 유지되어야만 합니다. 결국 n번째 호출 시점에는 (n-1)번째 호출에서 생성된 배열이 가비지 컬렉션되지 못하고 메모리에 남게 됩니다. 이 과정은 결국 메모리가 고갈될 때까지 반복됩니다.
Example
var newvalue;
function outFun() {
var array = new Array(1000000);
var value = newvalue;
function innFun() {
if (value) return array;
}
return function () {};
}
setInterval(function () {
newvalue = outFun();
}, 3);마무리: 메모리 누수 예방하기
메모리 누수를 예방하려면 변수를 항상 let, const 키워드로 명시적으로 선언하고, 'use strict' 모드를 활성화하며, 더 이상 필요하지 않은 참조는 null로 설정하여 가비지 컬렉터가 메모리를 회수할 수 있도록 해야 합니다. 또한 Chrome DevTools의 Memory 탭 등 프로파일링 도구를 활용해 힙 스냅샷을 정기적으로 확인하는 습관이 도움이 됩니다.