Chrome DevTools를 활용하여 Javascript 메모리 누수(Memory leak)를 디버깅하는 방법을 소개합니다.
Chrome DevTools에서 제공하는 Task Manager, Performance Profile, Memory Heap Snapshot, Memory Allocation Timline 기능을 사용하는 방법을 소개하고, 메모리릭을 진단하고 해결하는 몇 가지 경험적 노하우를 소개합니다.
위 슬라이드는 Grepp의 사내 세미나 불금개에서 활용하는 발표 자료입니다. CCL 라이센스를 따릅니다. 사용시 필수로 저작자를 명시하고, 영리적 사용을 금하며, 내용 변경이 불가합니다.
9. 메모리 이슈의 3가지 증상 9
1. 페이지가 시간이 지남에 따라 느려진다
시간이 지남에 따라 느리다고 느껴지는 경우 메모리 누수, 즉 메모리릭(Memory Leak)이 발생한다고 볼 수 있다.
2. 페이지가 일관되게 느리다
페이지가 처음부터 느리다고 느껴진다면, 이는 Memory bloat으로 볼 수 있다.
기본적으로 필요한 메모리보다 더 많은 메모리를 사용하는 것을 말한다.
3. 페이지가 뚝 뚝 끊긴다
가비지 콜렉션(Garbage Collection)이 너무 자주 일어날 때 발생한다.
GC가 돌면 모든 JS 스크립트의 실행이 중지되기 때문에 이 경우 잦은 GC를 의심해볼 수 있다.
10. 메모리 이슈의 3가지 증상 1
0
1. 페이지가 시간이 지남에 따라 느려진다
시간이 지남에 따라 느리다고 느껴지는 경우 메모리 누수, 즉 메모리릭(Memory Leak)이 발생한다고 볼 수 있다.
라이브 클래스룸은 1번 증상이었고
메모리릭이 문제일 것으로 예상하고 접근함.
11. 메모리 이슈의 3가지 증상 1
1
그래서 뭐가 문제가 되는지
어떻게 알 수 있는데?
13. 메모리 관련 Chrome DevTools 기능 – Task Manager 1
3
Chrome 우측 상단 메뉴 > More Tools > Task Manager
Task Manager는 열린 페이지의 실시간 메모리 사용량을 모니터링할 수 있는 도구이다.
중요한 곳은 Memory Footprint와 Javascript Memory 컬럼이다.
14. 메모리 관련 Chrome DevTools 기능 – Task Manager 1
4
• Memory Footprint : native memory 사용량을 보여준다. DOM 노드는 네이티브 메모리에
저장되는데, 만약 이 컬럼 값이 증가한다면 DOM 노드가 증가한다는 의미다.
• Javascript Memory : JS Heap 사용량을 보여준다. `()`로 감싸진 부분은 live number로
현재 페이지에서 접근 가능한 객체들이 사용중인 메모리를 뜻한다. 이 부분이 증가한다는
것은 객체가 새로 생성되거나, 이미 존재한 객체가 더 커진 걸 의미한다.
18. 메모리 관련 Chrome DevTools 기능 – Performance Profile 1
8
Chrome DevTools > Performance > Memory 체크박스 선택(활성화) > Record
Performance profile을 수행하면 시간에 따른 메모리 사용량을 각 항목별(JS Heap,
Documents, Nodes, Listeners)로 그려준다.
19. 메모리 관련 Chrome DevTools 기능 – Performance Profile 1
9
• 모든 항목에 대해서 그래프가 계단식으로 그려진 것을 확인했다.
• JS Heap에서 메모리릭이 발생하고 있고, 할당된 Listeners가 해제되지 않고 있다. 그리고
Documents, Nodes 가 쌓여간다.
페이지네이션 10회 시도 후 GC 시킨 과정을 기록한 performance profile
20. 메모리 관련 Chrome DevTools 기능
Memory Allocation Timeline
21. 메모리 관련 Chrome DevTools 기능 – Memory Allocation Timeline 2
1
Chrome DevTools > Memory > Profiles > Allocation instrumentation on timeline
Allocation Timeline은 시간에 따른 메모리 할당/해제를 막대 차트로 시각화해서 보여준다.
메모리 할당은 파란색 막대로, 메모리 해제는 회색 막대로 나타낸다.
22. 메모리 관련 Chrome DevTools 기능 – Memory Allocation Timeline 2
2
• 페이지네이션 할 때마다 파란색 막대가 생성되는 것을 확인했다. 회색 막대는
거의 보이지 않는다.
• 0~5초 사이에 파란 막대를 선택해보고 Retained Size 기준으로 내림차순
정렬하니 AnimationItem 생성자가 system 다음으로 가장 많은 메모리
차지한다. 해당 생성자는 lottie에서 호출하고 있다.
페이지네이션 10회 시도 후 GC 시킨 과정을 기록한 Allocation Timeline
23. 메모리 관련 Chrome DevTools 기능 – Memory Allocation Timeline 2
3
• 페이지네이션으로 인해 변경되는 컴포넌트를 살펴보니, 내부에서 Lottie.vue
를 사용하고 있었다.
• 해당 컴포넌트가 mount되면 lottie.loadAnimation() 함수를 호출해
애니메이션 인스턴스(AnimationItem)를 생성하고 ref에 저장하고 있었다.
• 그래서 해당 인스턴스를 unmount시에 destoy하는 로직을 추가했다. 결과는?
Lottie.vue 파일 내 setup 부분
24. 메모리 관련 Chrome DevTools 기능 – Lottie.vue 수정 후 Performance Profile 2
4
• Lottie.vue 파일 수정 후 performance profile 측정한 결과 GC가 돌때마다
JS Heap이 뚝 뚝 떨어지는 것을 확인했다!
• Documents, Nodes, Listeners는 여전히 계단식을 띔.
• 할당 후 해제하지 않은 Listeners가 있는지 먼저 살펴보자!
Lottie.vue 파일 수정 후 측정한 performance profile
25. 메모리 관련 Chrome DevTools 기능 – Listeners 디버깅 2
5
RepeatableLottie.vue 파일 setup 부분
• 컴포넌트들을 살펴보니 Lottie.vue를 wrapping한 RepeatableLottie.vue
컴포넌트를 사용하고 있었다.
• 해당 컴포넌트는 Lottie 애니메이션이 다음번 실행될
여부(playbackEnqueued)를 저장해뒀다가 애니메이션이
종료(complete)되었을 때 애니메이션을 다시 실행하도록 하기 위해
만들어진 것으로 보였다.
• 14번 라인에 ‘complete’ 이벤트 리스너를 추가하고 있지만 해제하는 라인은
보이지 않는다.
• 이벤트 리스너를 해제하는 로직을 추가하기로 했다. 또 로직을 좀 더 간단히
수정하기로 했다.
26. 2
6
RepeatableLottie.vue를 대체한 ClapAnimation.vue 컴포넌트 setup 부분
• `v-if` directive를 사용하여 필요한 경우에만 lottie를 렌더링하도록 하고,
unmount 할 때 complete 이벤트 리스너를 제거하도록 수정해보았다.
• 결과는?
메모리 관련 Chrome DevTools 기능 – Listeners 디버깅
27. 메모리 관련 Chrome DevTools 기능 – Lottie.vue 수정 후 Performance Profile 2
7
• RepeatableLottie.vue를 교체하고 이벤트 리스너를 제거해준 뒤
performance profile 측정한 결과 GC가 돌때 listeners가 감소했다!
• Documents, Nodes는 여전히 계단식을 띈다. Detached DOM이 있는지
살펴봐야 한다.
이벤트 리스너 제거 후 측정한 performance profile
28. 메모리 관련 Chrome DevTools 기능 – Detached DOM 2
8
Detached DOM:
DOM tree에서 제거되었지만 JS에 의해
여전히 reference 되고 있는 element
31. 3
1
메모리 관련 Chrome DevTools 기능 – Memory Heap Snapshots
Chrome DevTools > Memory > Profiles > Heap Snapshot
Heap Snapshot은 특정 순간의 JS 오브젝트와 DOM 노드 사이의 메모리 distribution을
보여준다.
이를 활용해서 Detached DOM을 찾아낼 수 있다.
32. 3
2
메모리 관련 Chrome DevTools 기능 – Memory Heap Snapshots
• 첫 번째 스냅샷은 Major GC후 초기 메모리 상태를 기록한 스냅샷
• 두 번째 스냅샷은 페이지 전환 10회 시도 후 메모리 스냅샷이다.
• 첫 번째 스냅샷과 두 번째 스냅샷에서 detached 키워드로 필터링한
결과를 비교하면, detached DOM들의 retained size가 일제히 증가한
것을 확인됨.
• 대부분의 detached DOM들이 SVG 관련 element 들로 확인됨.
초기 상태 힙 스냅샷
페이지네이션 10회 시도 후 GC 한 스냅샷
33. 3
3
메모리 관련 Chrome DevTools 기능 – Memory Heap Snapshots
SvgIcon.vue 컴포넌트를 사용하는 LiveClassProfile 컴포넌트
SvgIcon.vue
• SVG 관련 컴포넌트를 살펴보니 프로필 컴포넌트 내부에서 <SvgIcon />
컴포넌트를 사용하고 있었음
• <SvgIcon /> 컴포넌트는 svg definition을 불러와 image sprite 기법으로
svg를 사용하는 컴포넌트
• 이 컴포넌트를 제거하고 힙 스냅샷을 찍어보면?
34. 3
4
메모리 관련 Chrome DevTools 기능 – Memory Heap Snapshots
• 대부분의 detached SVG element가 제거된 것을 확인!
SvgIcon 컴포넌트 제거 후 페이지네이션 시도한 뒤 기록한 Heap Snapshot
36. 3
6
메모리 관련 Chrome DevTools 기능 – Memory Heap Snapshots
• <SvgIcon /> 컴포넌트를 별도의 inline svg 컴포넌트로 교체 후 스냅샷
기록한 결과 detached DOM들이 다시 생기긴 했지만, 초기 상태와 거의
유사한 수준
• Svg를 아예 없앨 수도 있지만(img 태그 활용해서), CSS로 svg의 색상을
변경하기위해 어쩔 수 없이 이 방식을 채택
SvgIcon 컴포넌트를 inline svg 컴포넌트로 교체 후 기록한 Heap Snapshot
38. 3
8
메모리 관련 Chrome DevTools 기능 – 디버깅 전/후 개선 결과 비교
개선 전 개선 후
스냅샷 1 스냅샷 2 델타
124MB 169MB +45MB
스냅샷 1 스냅샷 2 델타
127MB 118MB -9MB
39. 3
9
메모리 관련 Chrome DevTools 기능 – 디버깅 전/후 개선 결과 비교(Allocation Timeline)
Memory Allocation
+31MB
Memory Allocation
+990KB
개선 전 개선 후
40. 4
0
메모리 관련 Chrome DevTools 기능 – 디버깅 전/후 개선 결과 비교(Performance Profile)
개선 전 개선 후
항목 First GC 페이지네이션 & Second GC 델타
JS Heap 112MB 132MB +20MB
Documents 4개 14개 +10개
Nodes 3307개 17243개 +13936개
Listeners 459개 499개 +40개
항목 First GC 페이지네이션 & Second GC 델타
JS Heap 94MB 91MB -3MB(-100%)
Documents 5개 5개 +0개(-100%)
Nodes 4507개 5041개 +534개(-96%)
Listeners 473개 473개 +0개(-100%)
41. 4
1
메모리 관련 Chrome DevTools 기능 – 디버깅 전/후 개선 결과 비교(Allocation Timeline)
아직 개선의 여지가 남아있습니다.
그렇지만, 당장 급한 불은 껐습니다.
43. 정리 4
3
1. Lottie 애니메이션 인스턴스가 unmount시에 destroy 되도록 수정
2. Lottie 애니메이션 인스턴스에 추가한 이벤트 리스너가 unmount시에 remove 되도록 수정
3. <SvgIcon /> 컴포넌트를 별도 svg 컴포넌트로 교체
44. 내가 배운 내용 4
4
1. 메모리릭이 의심되면 Task Manager로 메모리를 모니터링 해보자
2. 큰 그림을 보려면 Chrome Performance Profile을 활용하자
3. JS Heap 메모리릭은 Memory Allocation Timeline으로 진단할 수 있다
4. Detached DOM은 2개 이상의 Heap Snapshot을 찍어서 비교하면 진단할 수 있다
45. References 4
5
• Chrome Developers - Record heap snapshots
• Chrome Developers - Memory terminology
• 자바스크립트에서 메모리 누수의 4가지 형태
• Vue 컴포넌트 - 메모리 누수 분석하기
• Finding and debugging memory leaks in JavaScript with Chrome DevTools
• Vue cookbook - Avoiding Memory Leaks
• Toast UI - 당신이 모르는 자바스크립트의 메모리 누수의 비밀