메모리 누수란?
부주의 또는 일부 프로그램 오류로 인해 더 이상 사용되지 않는 메모리를 해제하지 못하는 것이다.
자바스크립트의 메모리 관리
가비지 콜렉팅 언어(garbage collected languages) 인 자바스크립트는 이전에 할당된 메모리 영역이 응용프로그램의 다른부분에서 여전히 다시 참조될 수 있는지 주기적으로 확인하여 개발자가 메모리를 관리하는데 도움을 준다.
앞서 말한 메모리 누수의 정의에 따르면, 변수 또는 데이터가 더 필요하지 않을 때 이들은 가비지 변수 또는 가비지 데이터가 된다. 만약 그런 데이터가 메모리에 계속 쌓인다면, 결국에는 메모리 사용량을 초과하게 되어 이 시점에서 가비지 데이터를 정리 해야 한다.
주요 자바스크립트 메모리 누수 원인들
- 의도치 않은 전역 변수
전역 변수는 항상 루트에서 사용 살 수 있으며 가비지 수집되지 않는다. use strict 모드가 아닌 상태에서는 실수로 인해 로컬 범위에서 전역 범위로 변수가 노출된다.
선언되지 않은 변수에 값을 할당
function variables(arg) {
window.mistake = 'this is an explicit global variable'
}
전역 객체를 가리키는 ‘this’를 사용
function variables() {
this.mistake2 = 'potential accidental global'
}
// Foo가 호출되면, this는 글로벌 객체인 윈도우를 가리키게 된다.
variables()
이러한 실수가 일어나는 것을 막기 위해서는 엄격한 모드에서 파싱하게 하는 ”use strict” 를 자바스크립트 파일 맨 상단에 선언하면 실수를 방지 할 수 있다.
- 타이머 또는 콜백
콜백에서 일부 객체를 참조하는 setTimeout 또는 setInterval 을 갖는 것이 객체가 가비지 수집되는 것을 방지하는 가장 일반적인 방법이다. 코드에서 반복 타이머를 설정하면 콜백을 호출 할 수 없는 한 타이머 콜백의 객체에 대한 참조가 활성 상태를 유지한다.
타이머의 콜백에서 참조 된 객체를 인식하고 타이머에서 반환 된 핸들을 사용하여 취소하여 방지한다.
function setCallback() {
let counter = 0;
const hugeString = new Array(100000).join("x");
return function cb() {
counter++;
console.log(counter);
};
}
const timerId = setInterval(setCallback(), 1000);
clearInterval(timerId);
removeEventListener 활용하여 제거하여 방지한다.
var element = document.getElementById('button')
function onClick(event) {
element.innerHtml = 'text'
}
element.addEventListener('click', onClick)
element.removeEventListener('click', onClick)
element.parentNode.removeChild(element)
- 클로저
1초마다 replaceThing이 반복해서 호출되며 longStr과 someMethod 클로저를 생성되고 unused 변수는 originalThing을 참조하는 클로저를 가지게 된다.
이때, 스코프 체이닝에 의해 unused의 내부함수는 부모함수의 스코프를 공유한다.
여기서 unused 내부함수가 없었다면 매번 생성되는 longStr은 Mark-and-Sweep 알고리즘을 사용하는 최신 브라우저에서는 originalThing이 사용되지 않음을 파악하고 가비지컬렉팅의 대상이 된다.
그러나 unused 내부함수에서 originalThing을 계속해서 참조하고 이 때문에 메모리 누수가 일어나게 된다.
var theThing = null
var replaceThing = function () {
var originalThing = theThing
var unused = function () {
if (originalThing) console.log('hi')
}
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage)
},
}
}
setInterval(replaceThing, 1000)
- 분리된 DOM 노드
let btn = document.querySelector('button')
let child = document.querySelector('.child')
let root = document.querySelector('#root')
// root의 자식요소인 child을 DOM 트리에서 제거하였지만
// child 변수가 해당 요소를 참조하고 있어 메모리 누수의 원인이 된다.
btn.addEventListener('click', function() {
root.removeChild(child)
})
let btn = document.querySelector("button");
// 리스너의 콜백 함수 내부의 지역변수로 child를 정의하여 메모리 누수를 방지한다.
btn.addEventListener("click", function () {
let child = document.querySelector(".child");
let root = document.querySelector("#root");
root.removeChild(child);
});
- 콘솔 출력
개발 환경일 때 디버그 목적으로 콘솔을 출력할 수 있다.
하지만 실제 프로덕션 환경에서는 가능한 콘솔에 데이터를 출력하지 말아야 한다.
콘솔 출력은 출력하고자 하는 정보를 브라우저에 저장하기 때문에 메모리 누수의 원인이 된다.
'Javascript' 카테고리의 다른 글
현재 페이지 URL 가져오기 (1) | 2024.09.02 |
---|---|
SSE (text/event-stream) 적용 (0) | 2024.09.01 |
형제, 부모, 자식 노드 접근 (1) | 2024.08.28 |
HTML 태그(DOM) 접근하기 (0) | 2024.08.28 |
Promise.all() (0) | 2024.08.28 |