프린세스 다이어리

[JavaScript] 자바스크립트 메모리 누수를 막기 위한 방법들 본문

FE

[JavaScript] 자바스크립트 메모리 누수를 막기 위한 방법들

개발공주 2021. 10. 3. 12:07
728x90

1. 메모리 누수란?

 

메모리가 할당(allocated)된 상태에서, 프로그램의 어디에서도 참조(used)되지 않는데도 방출(release)되지 않은 상태로 계속 누적이 되면, 메모리 내에 프로세스 무게가 계속 더해져서 프로그램이 갑자기 종료되거나 멈춰버리는 주요한 원인이 된다. 자바스크립트에서는 기본적으로 가비지 콜렉터 기능을 가지고 있어, 프로그래머가 모든 변수를 필요 없어질 때마다 메모리 해제해 줘야 하는 부담은 덜었다. 하지만 애플리케이션이 복잡해지고 특정 라이브러리 또는 로직을 사용하여 코드를 짰을 때 의도대로 메모리가 정리되지 않고 지속적으로 쌓여 프로그램 성능이 저하될 가능성이 생긴다. 자바스크립트에서 대표적으로 메모리 누수를 막기 위해 어떤 방법을 사용할 수 있는지 정리해보았다.

 

2. 메모리 누수를 막기 위한 방법들

 

2-1. 전역 변수 할당 피하기

 

전역 변수란 브라우저의 window, Node.js의 global 같은 어디서든 접근 가능한 변수다. 

function foo(arg) {
	a = "some text";
}

위에서 a 라는 변수에 some text를 할당했다. 그런데 변수를 선언할 때 var, const, let 키워드를 이용하지 않으면, 자동으로 window라는 글로벌 오브젝트의 프로퍼티로 할당이 된다. 

function foo(arg) {
	window.a = "some text";
}

그렇게 되면 더 이상 foo만이 a를 참조하고 있는 게 아니라, 전체 애플리케이션의 코드에서 참조할 수 있게 되는 거다. foo 함수가 종료되고 나면 지역변수로 선언된 a라면 변수로 남아있지 않아야 하는데, 즉 메모리가 릴리즈 되어야 하는데, window의 프로퍼티로 등록이 되면 메모리가 사용되어지지 않음에도 릴리즈 되지 않는다. 의도대로 개발을 하려 한다면 변수선언 키워드를 써 주면 된다. 

 

function foo(arg) {
	const a = "some text";
}

 

실행 컨텍스트 안에서만 쓰는 변수를 선언하기 위해 다음과 같은 코드를 작성할 때도 있다.

function foo(arg) {
	this.a = "some text";
}

일반적인 함수를 선언했을 때, generator가 아닌 이 일반적인 함수에서는 이 this는 바로 window를 가리킨다. window.a와 똑같은 의미라는 거다. 사용이 끝난 후에는 null값을 넣어주는 것도 좋고, 그보다 앞서 프로그래머의 의도대로 해당 콘텍스트 안에서 변수를 선언하고 참조하려면 변수 선언 키워드를 붙여야 한다.

 

2-2. 타이머나 콜백을 남겨두지 않기

 

다음은 renderer가 존재하면 서버에서 내려주는 데이터를 renderer에 넣어주는 코드다. 먼저 잘못된 예시를 들어 보겠다.

const data = fetchData();
setInterval(function() {
	const renderer = document.getElementById('renderer');
    if (renderer) {
    	renderer.innerHTML = JSON.stringify(data);
    }
}, 1000);

 

이 코드의 문제점은 renderer 엘리먼트가 바뀔 수도 있고 없어질 수도 있다는 거다. 제거된, 바뀐 renderer에는 데이터가 참조될 일이 없는데도 불구하고 setinterval로 타이머를 만들어놨기 때문에 서버 데이터에 대한 참조는 없어지지 않고, 이 안의 코드 또한 없어지지 않는거다. 그래서 clearInterval 해줘야 한다. 엘리먼트도 사용이 끝났으면 removeElement로 다른 것으로 교체하거나 제거를 해 줘야 한다. 

const buttonEl = document.getElementById('launch-btn');
let counter = 0;

function onClick(e) {
	counter++;
    buttonEl.innerHTML = 'text ' + counter;
}

buttonEl.addEventListener('click', onClick);

...

buttonEl.removeEventListener('click', onClick); // <-
buttonEl.parentNode.removeChild(buttonEl); // <-

위와 같이, 엘리먼트에 이벤트핸들러를 등록해서 '...' 부분에서 로직을 실행시킨 후에는, removeEventHandler와 removeChild를 이용하면 된다. 더 이상 onclick이라는 function을 아무데서도 사용하지 않으면 자바스크립트의 가비지 콜렉터가 onClick을 컬렉팅 할 것을 알기 때문에 참조를 없애주는 것이다.

 

2-3. 콘솔로그

개발 환경일 때, 디버깅의 목적으로 콘솔을 출력하여 데이터가 잘 받아지고 있는지 확인하곤 한다. 그런데 받아오는 객체를 쉽게 확인할 수 있는 건 브라우저가 해당 객체의 정보를 저장하기 때문에 가능한 것이다. 블록이 실행이 끝났을 때 메모리에서 해제되어야 하는 변수들이, 콘솔에 출력이 되고 있기 때문에 가비지 콜렉팅이 일어나지 않는다. 상용 환경에서는 가능한 한 콘솔에 데이터를 출력하지 말아야 한다.

 

만약 정말 변수 출력을 원한다면 다음과 같이 작성할 수 있다.

if(isDev) { 
	console.log(obj) 
}

console.log, console.error, console.info, console.dir 등등 실제 환경에서 불필요한 변수를 출력하는 것을 사용하지 말아야 한다.

 

 

728x90
Comments