개발하면서 JS에 메모리 누수가 있었나?
최근에 JS 메모리 누수에 관한 이야기를 듣게 되었다.
여태까지 개발을 진행하면서 프론트쪽에서 메모리누수에 관련된 이슈를 겪어본 적이 없었기에 생각도 못했던 주제였다.
출시했던 모든 제품에서 성능적인 문제는 대부분 서버에서 왔고, 프론트엔드쪽을 내가 개발을 하지 않았을 때 이긴 하지만 프론트엔드의 성능적인 부분은 전부 부자연스러움 이었기 때문.
(해당 내용으로 인해서 기존 팀원이었던 직원들에게 많이 쓴소리를 하기도 하였다.)
1. 의도치 않은 전역변수로 인한 메모리 수누
javascript function test() {
text = "test"
}
test()

첫번째로, 내가 겪어보진 못했지만 JS 메모의 누수의 가장 대표적인 예.
진짜 생각도 못한 부분이었는데, 함수 내에 변수 타입의 지정 없이 (var, let, const) 그냥 선언했을 경우 이다.
test function 안에 text 라는 변수가 선언되었고, test 함수를 실행 하고 웹 브라우저에서 window.text를 출력하니, test가 출력되었다.
즉, 함수 내에서 변수 타입을 선언하지 않고 변수를 초기화 하였을 때, window 객체 안에 담기게 되어서 함수가 종료 됨에도 불구하고 함수 내에서 선언된 변수가 메모리를 차지하게 된다.
해당 경우는 대표적인 예지만, 일단 지난 기간동안 내가 겪어보지 못한 타입이다.
이 이슈를 화두로 JS 메모리 누수에 관련된 이야기가 나왔었는데, 아마 내가 겪어보지 못한 이유는 생각컨데 다음과 같다고 생각된다.
- JS로 프로그래밍을 처음 배우지 않았다. C언어로 시작하였고, 이후에는 PHP를 주력으로 실무적인 개발을 진행했었다.
- ESLint, 향상된 에디터 기능
일단 첫번째로는, 애시당초에 JS로 개발을 시작하지 않았기에 나는 변수 타입을 선언하고 변수를 초기화 하는게 익숙했다.
퍼블리싱으로 먼저 JS를 접한 분들이라면 충분히 낼 수 있는 오류 일 것 같다.
두번째로는, 요즘 에디터 기능들이 굉장히 좋아지기도 했고 ESLint를 적용했다면 에디터에서 변수 타입이 선언되지 않았으면 위 코드를 오류메시지를 출력한다. (에디터 내에서의 밑줄 형태로.)
강업적으로 진행되기 때문에, 아무리 생각해봐도 내가 위처럼 변수를 선언하는 경우는 생각나지 않았다.
혹은 use strict를 이용한다는 방법도 있어 아마 요즘 위 같은 경우로 메모리 누수를 겪으시는 분들은 없을 것 같다.
2. 잊혀진 타이머 또는 콜백 함수
JS 메모리 누수로 관련되어 나오는 대표적인 두번째 예.
예제 코드를 살펴보면 다음과 같다.
javascriptvar data = getData();
setInterval(function(){
var tag = document.getElementById('result');
if(tag){
tag.innerHTML = data;
}
}, 1000);
흠.. 이건 처음부터 지금까지도 솔직히 이해가 되지 않는다.
무한적으로 setInterval을 돌리는 경우가 아니라면 위와같이 짜는 분이 있을까...? 싶긴 한데 생각보다 꽤나 있나보다.
getData()는 서버에서 데이터를 가져온 것이라고 가정한다면, 일반적으로는 서버 통신 이후에
document.getElementById('result')에 접근하여 html 내용을 바꿔 줄 것이다. ajax든, axios든 사용한다면 success 안쪽에서 일어나거나, await/async, 혹은 promise를 사용해서 처리를 할 것이다.
즉 setInterval을 돌리지 않아도 된다는 것이고 위 코드는 당연히 setInterval을 해제하지 않기 때문에 tag에 텍스트를 넣고서도 주기적으로 돌아갈 것이다..
흠...
일단 진짜 무한정 돌아가야 하는경우가 아니라면 위처럼 해본적이 없어서 다행히 해당사항이 없는 것 같다.
DOM 외부 참조
다음은 Object, Array등에서 DOM을 참조했을 때 일어나는 메모리 누수건이다.
javascriptvar elemMap = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function removeButton() {
document.body.removeChild(document.getElementById('button'));
}
removeButton 함수가 실행되었다고 가정한다면 DOM에서 id 값이 button인 엘레먼트는 삭제 되었을 것이다.
하지만 elemMap 안쪽의 button을 초기화 하거나 해주지 않았다. 아직 계속 참조중.
굳이 Object나 Array 안쪽에 DOM을 참조한게 아니더라도 a라는 변수에 DOM을 참조하게끔 하고 해당 엘레먼트를 지워보면 a 변수 안에는 남아있는걸 확인이 가능했다.

엘레먼트 자체를 날려버릴때에는, 지정된 변수가 있다면 해당 변수도 null 등을 초기화 해서 메모리 누수를 막게끔 처리 해야겠다.
일반적으로 remove element를 잘 사용하지 않았었지만, 만약 위처럼 개발해야되는 일이 있다면 꼭 기존 변수도 날려주도록 하자.
4. closure 메모리 누수
마지막으로 closure에 관한 메모리 누수건이다.
javascriptvar theThing = null;
var cnt = 0;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) console.log("hi");
};
theThing = {
id: cnt++,
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('someMessage');
}
};
};
setInterval(replaceThing, 1000);
가장 널리 알려진 예제. 스코프에 대한 문제로 유명하다.
가장 어려운 개념인 것 같고 이런 방식으로 메모리누수가 실질적으로는 많이 이뤄질 것 같다.
이건 나도 기존에 위와같은 방식으로 개발한 파트가 있는지 살펴봐야 알 수 있을 것 같은데,
replaceThing 내부에서 일단 theThing을 초기화하고, theThing.someMethod를 클로저로 선언하였다.
여기서 replaceThing 내부에서 originalThing 안에 theThing을 대입해 주었는데, 여기서 originalThing을 참조하게 된다.
심지어 unused 는 사용되지 않는 함수지만 originalThing에 접근하였고 이는 theThing을 참조한다.
여기서, replaceThing이 종료되더라도 theThing을 통해서 replaceThing의 로컬스코프가 해제되지 않아 메모리 누수로 이어지게 된다.
개발하면서 JS에 메모리 누수가 있었나?
최근에 JS 메모리 누수에 관한 이야기를 듣게 되었다.
여태까지 개발을 진행하면서 프론트쪽에서 메모리누수에 관련된 이슈를 겪어본 적이 없었기에 생각도 못했던 주제였다.
출시했던 모든 제품에서 성능적인 문제는 대부분 서버에서 왔고, 프론트엔드쪽을 내가 개발을 하지 않았을 때 이긴 하지만 프론트엔드의 성능적인 부분은 전부 부자연스러움 이었기 때문.
(해당 내용으로 인해서 기존 팀원이었던 직원들에게 많이 쓴소리를 하기도 하였다.)
1. 의도치 않은 전역변수로 인한 메모리 수누
javascript function test() {
text = "test"
}
test()

첫번째로, 내가 겪어보진 못했지만 JS 메모의 누수의 가장 대표적인 예.
진짜 생각도 못한 부분이었는데, 함수 내에 변수 타입의 지정 없이 (var, let, const) 그냥 선언했을 경우 이다.
test function 안에 text 라는 변수가 선언되었고, test 함수를 실행 하고 웹 브라우저에서 window.text를 출력하니, test가 출력되었다.
즉, 함수 내에서 변수 타입을 선언하지 않고 변수를 초기화 하였을 때, window 객체 안에 담기게 되어서 함수가 종료 됨에도 불구하고 함수 내에서 선언된 변수가 메모리를 차지하게 된다.
해당 경우는 대표적인 예지만, 일단 지난 기간동안 내가 겪어보지 못한 타입이다.
이 이슈를 화두로 JS 메모리 누수에 관련된 이야기가 나왔었는데, 아마 내가 겪어보지 못한 이유는 생각컨데 다음과 같다고 생각된다.
- JS로 프로그래밍을 처음 배우지 않았다. C언어로 시작하였고, 이후에는 PHP를 주력으로 실무적인 개발을 진행했었다.
- ESLint, 향상된 에디터 기능
일단 첫번째로는, 애시당초에 JS로 개발을 시작하지 않았기에 나는 변수 타입을 선언하고 변수를 초기화 하는게 익숙했다.
퍼블리싱으로 먼저 JS를 접한 분들이라면 충분히 낼 수 있는 오류 일 것 같다.
두번째로는, 요즘 에디터 기능들이 굉장히 좋아지기도 했고 ESLint를 적용했다면 에디터에서 변수 타입이 선언되지 않았으면 위 코드를 오류메시지를 출력한다. (에디터 내에서의 밑줄 형태로.)
강업적으로 진행되기 때문에, 아무리 생각해봐도 내가 위처럼 변수를 선언하는 경우는 생각나지 않았다.
혹은 use strict를 이용한다는 방법도 있어 아마 요즘 위 같은 경우로 메모리 누수를 겪으시는 분들은 없을 것 같다.
2. 잊혀진 타이머 또는 콜백 함수
JS 메모리 누수로 관련되어 나오는 대표적인 두번째 예.
예제 코드를 살펴보면 다음과 같다.
javascriptvar data = getData();
setInterval(function(){
var tag = document.getElementById('result');
if(tag){
tag.innerHTML = data;
}
}, 1000);
흠.. 이건 처음부터 지금까지도 솔직히 이해가 되지 않는다.
무한적으로 setInterval을 돌리는 경우가 아니라면 위와같이 짜는 분이 있을까...? 싶긴 한데 생각보다 꽤나 있나보다.
getData()는 서버에서 데이터를 가져온 것이라고 가정한다면, 일반적으로는 서버 통신 이후에
document.getElementById('result')에 접근하여 html 내용을 바꿔 줄 것이다. ajax든, axios든 사용한다면 success 안쪽에서 일어나거나, await/async, 혹은 promise를 사용해서 처리를 할 것이다.
즉 setInterval을 돌리지 않아도 된다는 것이고 위 코드는 당연히 setInterval을 해제하지 않기 때문에 tag에 텍스트를 넣고서도 주기적으로 돌아갈 것이다..
흠...
일단 진짜 무한정 돌아가야 하는경우가 아니라면 위처럼 해본적이 없어서 다행히 해당사항이 없는 것 같다.
DOM 외부 참조
다음은 Object, Array등에서 DOM을 참조했을 때 일어나는 메모리 누수건이다.
javascriptvar elemMap = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function removeButton() {
document.body.removeChild(document.getElementById('button'));
}
removeButton 함수가 실행되었다고 가정한다면 DOM에서 id 값이 button인 엘레먼트는 삭제 되었을 것이다.
하지만 elemMap 안쪽의 button을 초기화 하거나 해주지 않았다. 아직 계속 참조중.
굳이 Object나 Array 안쪽에 DOM을 참조한게 아니더라도 a라는 변수에 DOM을 참조하게끔 하고 해당 엘레먼트를 지워보면 a 변수 안에는 남아있는걸 확인이 가능했다.

엘레먼트 자체를 날려버릴때에는, 지정된 변수가 있다면 해당 변수도 null 등을 초기화 해서 메모리 누수를 막게끔 처리 해야겠다.
일반적으로 remove element를 잘 사용하지 않았었지만, 만약 위처럼 개발해야되는 일이 있다면 꼭 기존 변수도 날려주도록 하자.
4. closure 메모리 누수
마지막으로 closure에 관한 메모리 누수건이다.
javascriptvar theThing = null;
var cnt = 0;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) console.log("hi");
};
theThing = {
id: cnt++,
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('someMessage');
}
};
};
setInterval(replaceThing, 1000);
가장 널리 알려진 예제. 스코프에 대한 문제로 유명하다.
가장 어려운 개념인 것 같고 이런 방식으로 메모리누수가 실질적으로는 많이 이뤄질 것 같다.
이건 나도 기존에 위와같은 방식으로 개발한 파트가 있는지 살펴봐야 알 수 있을 것 같은데,
replaceThing 내부에서 일단 theThing을 초기화하고, theThing.someMethod를 클로저로 선언하였다.
여기서 replaceThing 내부에서 originalThing 안에 theThing을 대입해 주었는데, 여기서 originalThing을 참조하게 된다.
심지어 unused 는 사용되지 않는 함수지만 originalThing에 접근하였고 이는 theThing을 참조한다.
여기서, replaceThing이 종료되더라도 theThing을 통해서 replaceThing의 로컬스코프가 해제되지 않아 메모리 누수로 이어지게 된다.