일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- SSR
- ts error
- vue-cli
- rendering
- https
- web vital
- 선택자
- custom command
- aws
- Cypress
- svelte
- vue3
- CSS
- csr
- 비동기
- Testing
- QUIC
- http3
- vue
- caching
- import.meta.env
- typeScript
- msw
- TLS
- devtools
- e2e
- api test
- JavaScript
- CloudFlare
- ViTE
- Today
- Total
Develop Note by J.S.
[Programming] Memory Leak 유형 & 식별방법 본문
Memory Leak, GC에 이어 GC에서 걸러지지 않은 Memory Leak의 유형 및 식별 방법에 대하여 알아보겠습니다.
1. Memory Leak 유형
1) 선언되지 않은 변수의 참조 또는 참조가 없는 변수를 생성
- 선언되지 않은 변수를 참조한 경우에는 컴파일 과정에서 전역 객체(Root)에서 새 변수를 생성합니다.
function foo(){
this.message = 'I am accidental variable';
}
foo();
Mark-and-Sheep 알고리즘에서는 Root를 가리키는 변수는 항상 active 상태이기 때문에 GC가 이를 제거할 수 없어 메모리 누수가 발생합니다.
- 코딩 시점에 사용되지 않거나 선언없이 참조한 코드를 삭제하거나, 자바스크립트의 use strict Mode를 활성화하여 방지 할 수 있습니다.
2) Closer
- Closer는 선언 시점의 렉시컬 환경 정보를 기억하는 함수로, 간단히 말해서 내부함수가 외부함수에 접근할 수 있는 것을 Closer라고 합니다. (Lexical Environment : 선언 시점에서의 지역변수, 매개변수 및 상위 Scope 등의 주변 환경)
- 외부 함수의 변수를 사용하지 않더라도 Closer에 의해 메모리에 남아 있기 때문에 메모리 누수가 발생될 수 있습니다.
function outer(){
const largeArray = []; // unused array
return function inner(num){
largeArray.push(num);
}
}
const appendNumbers = outer(); // get the inner function
// call the inner function repeatedly
for (let i=0; i< 100000000; i++){
appendNumbers(i);
}
- largeArray의 경우 GC에서 접근할 수 없고, 반환되거나 사용되지 않기 때문에, Closer를 사용하는 개발 과정에서 메모리 누수방지를 위한 별도의 체크가 필요합니다.
3) Forgetten Timers
- setTimeout, setInterval과 같은 Javascript 타이머 실행 이후 사용하지 않음에도 중지시키지 않을 때 메모리 누수가 발생됩니다.
function generateRandomNumbers(){
const numbers = []; // huge increasing array
return function(){
numbers.push(Math.random());
}
}
setInterval((generateRandomNumbers(), 2000));
- 따라서 미사용 타이머는 clearInterval(), cleartimeout()를 사용하여 타이머를 중지해야 합니다.
4) Dom 외부참조
- Dom에서는 제거되었지만 메모리에 남아있는 노드로 인해 메모리 누수가 발생 될 수 있습니다.
let parent = document.getElementById("#parent");
let child = document.getElementById("#child");
parent.addEventListener("click", function(){
child.remove(); // removed from the DOM but not from the object memory
});
- parent Click 이벤트로 child 노드를 삭제하여도 parent의 이벤트 리스너는 child 노드를 항상 참조하고 때문에 GC에서 걸러지지 않고 메모리에 남아 있습니다. 따라서 removeEventListener로 이벤트를 제거해야 합니다.
function removeChild(){
child.remove();
}
parent.addEventListener("click", removeChild);
// after completing required action
parent.removeEventListener("click", removeChild);
2. Memory Leak 식별
- 브라우저 앱의 DevTools에서 제공하는 Memory Graph 및 Perfomance 기능으로 메모리 누수를 식별 할 수 있습니다.
1) Perfomance Timeline (메모리 소비 시각화)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Memory leaks</title>
</head>
<body>
<button id="print">Print Numbers</button>
<button id="clear">Clear</button>
</body>
</html>
<script>
var longArray = [];
function print() {
for (var i = 0; i < 10000; i++) {
let paragraph = document.createElement("p");
paragraph.innerHTML = i;
document.body.appendChild(paragraph);
}
longArray.push(new Array(1000000).join("y"));
}
document.getElementById("print").addEventListener("click", print);
document.getElementById("clear").addEventListener("click", () => {
window.longArray = [];
});
</script>
- 예제의 Print Number는 <p> 단락 노드를 생성하하면서 전역변수 longArray에 대량의 데이터를 푸시합니다. 이후 clear를 통해 전역변수는 초기화하고 단락 노드는 그대로 두었습니다.
- 해당 이미지는 Chrome DevTools - Perfomance 타임라인의 스크린샷입니다. 테스트 Action은 Print Number와 Clear버튼을 교차하며 클릭하였는데요, Print Number를 클릭 할 때마다 파란색인 Heap 그래프가 스파이크되고, Clear이후 사라졌습니다. 하지만 생성한 Node는 제거하지 않았기 때문에 지속적으로 증가하는 것을 알 수 있습니다.
- 실제 개발 과정에서 해당 기능으로 체크할 때에 메모리가 소비가 지속적으로 감소되지 않는 경우에 메모리 누수를 의심해볼 수 있습니다.
* 병목현상 추적
- Perfomance 탭 하단 Summary의 Doughnut Chart는 CPU사용량을 나타내며, 해당 차트는 Rendering 처리시간이 가장 긴것을 알 수 있습니다.
- Main Section을 펼치면 x축은 시간, y축은 동작된 이벤트를 표기합니다.
- 분석하고 싶은 지점을 마우스 휠로 확대할 수 있으며 해당 타임구간에 발생된 이벤트를 선택할 경우 Summary에서 이벤드가 발생된 정확한 코드 위치를 알 수 있습니다.
2) Memory Graph
가) Heap Snapshot
- DOM트리에서 제거된 노드 중 참조에 의하여 메모리에 남아 있는 경우(detached Dom)에 Heap SnapShots에서 Detached 키워드로 필터링하여 메모리 누수를 확인할 수 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Memory leaks</title>
</head>
<body>
<button id="createList">createList</button>
</body>
</html>
<script>
var detachedElement;
function createList(){
let ul = document.createElement("ul");
for(let i=0; i<5; i++){
ul.appendChild(document.createElement("li"));
}
detachedElement = ul;
}
document.getElementById("createList").addEventListener("click", createList);
</script>
- createdList 버튼 클릭 시, 전역변수로 인하여 참조된 상태지만 Dom에는 보여주지 않는(detached Dom) ul, li 노드를 생성하는 예제입니다.
- DevTools Memory -> Heap Snapshot 에서 detached 키워드로 검색할 시 확인할 수 있습니다.
나) Allocation Timeline
- 메모리 할당 Timeline을 볼수 있습니다. 녹화 시작 후 메모리 할당 측정을 위해 라우터 전환을 반복한 뒤 메모리가 반환되지 않고 남아 있는 경우(그래프의 파란색영역)를 체크할 수 있습니다.
참고사이트
'Knowledge > Programming' 카테고리의 다른 글
[Programming] eslint linebreak Error (0) | 2023.08.22 |
---|---|
[Programming] Memory Leak (메모리누수 with V8) (3) | 2023.08.21 |
[Programming] 정렬(힙 정렬 제외 오름차순 정렬 기준) (1) | 2023.07.10 |