Memory Leak
메모리 누수(Memory Leak)
메모리 누수(Memory Leak)란 더 이상 필요하지 않은 데이터가 메모리에서 해제되지 못하고 메모리를 계속 차지되는 현상입니다.
[메모리 누수가 발생하는 상황] *잘못된 코드 작성 습관은 메모리 누수로 연결됩니다.
- 불필요한 전역 변수 사용
window.ex = "hello world";
window.rati = { name: "rati", age: 5 };
불필요한 전역 변수 사용의 예로 전역 객체 window에 선언을 한 경우 브라우저에 항상 존재해야 하기 때문에 데이터가 더 이상 필요하지 않음에도 불구하고 해제되지 못하고 메모리 누수를 발생시킵니다.
- 분리된 노드 참조
// html
<body>
<button>Remve!</button>
<div class="parent">
<div class="child1">1</div>
<div class="child2">2</div>
</div>
</body>
// js
const btn = document.querySelector("button");
const parent = document.querySelector(".parent");
btn.addEventListener("click", () => {
console.log(parent);
parent.remove();
});
Remove 버튼을 클릭하게 되면 요소 parent가 콘솔에 출력되고 해당 요소가 제거되도록 해주었습니다. 실제로 화면에서 해당 요소는 제거가 되었지만 버튼을 추가적으로 클릭하게 되면 콘솔에는 해당 요소가 계속해서 출력되는 것을 확인할 수 있습니다.
위의 예시가 분리된 노드를 참조하여 메모리 누수가 발생한 대표적인 예시이며, 이러한 상황이 발생한 원인은 상수 parent에 클래스가 parent를 찾아 할당을 하여 메모리 공간에 저장이 된 상태에서 remove 메서드를 통해 해당 요소를 제거를 하더라고 해당 요소가 가지고 있는 메모리 공간의 주소는 제거된 것이 아니기 때문에 상수 parent는 가비지 컬렉션에 의해 제거되지 못하게 됩니다.
const btn = document.querySelector("button");
btn.addEventListener("click", () => {
const parent = document.querySelector(".parent");
console.log(parent);
parent && parent.remove();
});
해결 방법은 간단합니다. 클래스가 parent인 요소를 찾아 할당하는 코드를 이벤트 내부에 작성해주게 되면 버튼을 처음 클릭했을 때 해당 요소를 찾아 메모리 공간에 저장하지만, 이후 버튼 클릭 시 해당 요소를 찾지 못해 상수 parent에는 null이 할당되게 되게 됩니다.
더 이상 상수 parent는 사용되지 않고 null 값을 가지고 있기 때문에 가비지컬렉션에 의해 해제가 됩니다.
- 해체하지 않은 타이머
let a = 0;
setInterval(() => {
a += 1;
}, 100);
setTimeout(() => {
console.log(a);
}, 1000);
함수 setInterval를 사용하여 0.1초에 한 번씩 변수 a의 값에 1을 더해주고 있으며, 함수 setTimeout를 사용하여 1초 뒤에 변수 a가 출력되도록 해주었습니다. 이때 변수 a는 계속해서 증가하고 있기 때문에 setInterval이 동작하고 있는 동안은 계속해서 메모리를 차지하게 됩니다.
let a = 0;
const intervalId = setInterval(() => {
a += 1;
}, 100);
setTimeout(() => {
console.log(a);
clearInterval(intervalId);
}, 1000);
메모리 누수를 방지하기 위해 함수 setTimeout에서 변수 a 사용이 끝나면 clearInterval을 사용하여 interval을 해제해주어야 합니다.
- 잘못된 클로저 사용
const getFn = () => {
let a = 0;
return (name) => {
a += 1;
console.log(a);
return `Hello ${name}~`;
};
};
const fn = getFn();
console.log(fn("rati1"));
console.log(fn("rati2"));
console.log(fn("rati3"));
정작 필요한 부분은 Hello + 인수로 전달받은 문자가 출력되는 부분인데 불필요한 a+=1, a 콘솔 출력으로 렉시컬 범위에 있는 변수 a를 계속해서 사용하고 있기 때문에 가비지 컬렉션이 해당 변수를 해제하지 못하고 메모리를 계속 차지하고 있는 상황이 발생하게 됩니다.