생각해봐야 할 질문
일반적으로 메모리 누수 문제가 발생했다고 생각되면 다음 세 가지 질문에 대해 생각해야 합니다.
내 페이지가 메모리를 너무 많이 차지하나요? - 시간라인 메모리 보기 도구(타임라인 메모리 보기)와 Chrome 작업 관리자(Chrome 작업 관리자)를 사용하면 더 많은 메모리를 사용했는지 확인할 수 있습니다. . 메모리 뷰는 페이지 렌더링 중 DOM 노드 개수, document의 문서 개수 및 JS 이벤트 청취 횟수를 추적할 수 있습니다. 경험상 더 이상 필요하지 않은 DOM 요소에 대한 참조를 피하고, 불필요한 이벤트 리스너를 제거하고, 사용하지 않을 대용량 데이터를 저장할 때는 주의하세요.
내 페이지에 메모리 누수가 있나요? - 객체할당 추적(객체 모두 위치 추적기)는 JS 개체 할당을 실시간으로 확인하여 누수 위치를 찾는 데 도움이 됩니다. 또한 힙 분석기(Heap Profiler)를 사용하여 JS 힙 스냅샷을 생성하고 메모리 맵을 분석하고 스냅샷 간의 차이점을 비교하여 가비지 수집으로 정리되지 않은 개체를 찾을 수 있습니다.
내 페이지는 얼마나 자주 가비지 수집되나요? - 페이지가 자주 가비지 수집되는 경우 페이지가 너무 자주 할당될 수 있습니다. 타임라인 메모리 보기는 관심 있는 일시 중지를 찾는 데 도움이 됩니다.
용어 및 기본 개념
이 섹션은 기억 분석에 소개되어 있습니다. 다른 언어의 메모리 분석 도구에 사용되는 일반적인 용어입니다. 여기에 나온 용어와 개념은 힙 프로파일러 UI 도구 및 관련 문서에서 사용됩니다.
이는 메모리 분석 도구를 효과적으로 사용하는 방법에 익숙해지는 데 도움이 됩니다. Java, .NET 등의 언어에 대한 메모리 분석 도구를 사용해 본 적이 있다면 리뷰가 될 것입니다.
객체 크기
메모리를 기본 유형(예: 숫자 및 문자열)과 객체(연관 배열) 차트의 모음으로 생각하세요. 일련의 관련 사항을 나타내는 다음 다이어그램과 유사할 수 있습니다.
객체에는 메모리를 사용하는 두 가지 방법이 있습니다.
객체 자체가 메모리를 직접 사용합니다.
-
다른 개체에 대한 참조를 암시적으로 유지합니다. 이 방법을 사용하면 가비지 수집(GC)이 해당 개체를 자동으로 재활용하는 것을 방지할 수 있습니다.
DevTools의 힙 프로파일러(DevTools의 "Profile" 탭에 있는 메모리 문제를 분석하는 데 사용되는 도구인 힙 프로파일러)를 사용하면 다음과 같은 열을 발견하고 놀랄 수 있습니다. 다양한 정보를 표시합니다. 그 중 두 가지는 직접 메모리 점유(얕은 크기) 및 총 메모리 점유(보유 크기) 입니다.
직접 메모리 점유(Shallow Size, 참조 객체가 점유하는 메모리 제외)
객체 자체가 점유하는 메모리입니다.
일반적인 JavaScript 개체에는 개체를 설명하고 개체의 직접 값을 저장하기 위해 예약된 메모리가 있습니다. 일반적으로 배열과 문자열만 메모리를 직접적으로 많이 차지합니다(얕은 크기). 그러나 문자열과 배열은 종종 기본 데이터 부분을 렌더러 메모리에 저장하고 JavaScript 개체 스택에 작은 래퍼 개체만 노출합니다.
렌더러 메모리는 분석하는 페이지의 렌더링 프로세스에 사용되는 모든 메모리를 말합니다. 페이지 자체의 메모리 + 페이지의 JS 힙에서 사용되는 메모리 + 트리거된 관련 작업자 프로세스(작업자) 페이지별 JS 힙에서 사용되는 메모리입니다. 그러나 작은 객체는 쓰레기에 의해 자동으로 다른 객체가 수집되는 것을 방지함으로써 간접적으로 많은 양의 메모리를 차지할 수 있습니다.
점유된 총 메모리(보유된 크기, 참조된 객체가 점유한 메모리 포함)
객체가 삭제되면 객체가 참조하는 종속 객체는 GC가 될 수 없습니다. 루트(GC 루트)는 이를 참조하며, 개체가 차지하는 전체 메모리에는 이러한 종속 개체가 차지하는 메모리가 포함됩니다.
GC 루트는 컨트롤러(handles)로 구성됩니다(로컬 또는 글로벌). V8 엔진 외부의 JavaScript 객체에 대한 참조가 내장함수(네이티브 코드)에 의해 설정될 때 생성됩니다. 이러한 컨트롤러는 모두 힙 스냅샷의 GC 루트(GC 루트) > 핸들 범위 및 GC 루트 >전역 핸들러 에서 찾았습니다. 이 기사에서 이러한 컨트롤러를 소개하는 것은 브라우저 구현에 대한 깊은 이해가 없으면 혼란스러울 수 있습니다. GC 루트와 컨트롤러에 대해 너무 걱정할 필요가 없습니다.
사용자에게 중요하지 않은 내부 GC 루트가 많이 있습니다. 애플리케이션 관점에서 보면 다음과 같은 상황이 있습니다.- Window 전역 객체(모두
- 문서가 통과할 수 있는 모든 DOM 노드로 구성된 문서 DOM 트리입니다. 모든 노드가 해당 JS에서 참조되는 것은 아니지만 JS에서 참조되는 노드는 문서가 존재하는 한 유지됩니다.
- 코드를
디버깅할 때 또는 DevTools 콘솔에서(예: 콘솔의 일부 코드가 실행된 후) 생성될 수 있는 개체가 많이 있습니다.
참고: 힙 스냅샷을 생성할 때 사용자가 콘솔에서 코드를 실행하거나 디버깅 중단점을 활성화하지 않는 것이 좋습니다.
메모리 맵은 브라우저의 객체 또는 window
Node.js 모듈 객체일 수 있는 루트로 시작합니다. 이러한 개체를 메모리에서 회수하는 방법은 사용자가 제어할 수 없습니다. Global
참고: 직접 점유된 메모리 필드와 총 메모리 점유 필드의 데이터는 바이트 단위로 표시됩니다.
객체가 차지하는 전체 메모리 트리우리는 이전에 힙이 상호 관련된 다양한 객체로 구성된 네트워크 구조임을 배웠습니다. 디지털 세계에서는 이러한 구조를그래프 또는 메모리 그래프라고 부른다. 그래프는 에지로 연결된 노드로 구성되며 모두 레이블이 지정됩니다.
의 이름입니다. 이 문서에서는 힙 분석기를 사용하여 스냅샷을 생성하는 방법을 배웁니다. 아래 그림의 힙 분석기에 의해 생성된 스냅샷에서 거리 필드를 볼 수 있습니다. 거리 필드는 객체에서 GC 루트까지의 거리를 나타냅니다. 동일한 유형의 모든 객체가 동일한 거리에 있지만 작은 하위 집합이 더 멀리 떨어져 있는 경우 조사해야 할 문제가 있을 수 있습니다.도미네이터
도미네이터는 각 개체에 도미네이터가 있으므로 트리 구조와 같습니다. 객체의 컨트롤러는 자신이 지배하는 객체를 직접 참조할 수 없습니다. 즉, 지배 객체 트리 구조는 그래프의 스패닝 트리가 아닙니다.
위 그림에서:
노드 1이 노드 2를 지배합니다
노드 2가 노드 3, 4, 6을 지배
노드 3이 노드 5를 지배
노드 5가 노드 8을 지배
노드 6이 노드 7을 지배합니다
아래 예에서 노드 #3
는 #10
의 지배적인 플레이어이지만 #7
은 모든 노드에서 지배적인 노드이기도 합니다. #10
에 대한 GC가 경로에 나타납니다. 이와 같이 루트 노드에서 A 객체까지의 모든 경로에 B 객체가 나타나면 B 객체가 A 객체의 지배적 객체가 됩니다.
V8 소개
이 섹션에서는 V8 JavaScript 가상 머신 (V8 VM 또는 VM) 관련. 메모리를 분석할 때 힙 스냅샷을 이해하려면 이러한 개념을 이해하는 것이 도움이 됩니다.
JavaScript 개체 설명 에는 세 가지 기본 유형이 있습니다.- 숫자(예: 3.14159..)
- 부울(참 또는 거짓)
- 문자 유형(
숫자는 다음 두 가지 방법 중 하나로 저장됩니다.
- 31비트
- 힙 개체,
힙 값으로 참조됨 . 힙 값은 double과 같이 SMI 저장에 적합하지 않은 데이터를 저장하거나 이 값과 같이 값을 boxing해야 하는 경우에 사용되며, 속성 값.
문자 데이터는
-
VM 힙 두 가지 방식으로 저장됩니다. 또는 외부 렌더러 메모리
의 . 이때 VM 힙에 직접 복사되는 대신 웹 페이지 패키지에 저장된 스크립트 리소스, 기타 콘텐츠 등 저장 위치에 액세스하기 위해 래퍼 개체가 생성됩니다.
VM 힙)에 메모리가 할당됩니다. 이러한 객체는 V8의 가비지 수집기에 의해 관리되며 이에 대한 강력한 참조가 있는 한 메모리에 유지됩니다.
로컬 객체는 모두 JavaScript 힙에 없는 객체입니다. 힙 객체와 달리 라이프사이클 동안 V8에 의해 가비지 수집되지 않습니다. JavaScript를 통해 객체 참조를 래핑합니다.
연결 문자열 은 한 쌍의 문자열로 병합된 개체이며 병합의 결과입니다. 연결 문자열은 필요한 경우에만 병합됩니다. 연결된 문자열과 마찬가지로 부분 문자열을 구성해야 합니다.
예를 들어a와 b를 연결하면 연결 결과를 나타내는 데 사용되는 문자열 (a, b)를 얻습니다. 나중에 이 결과를 d와 연결하면 또 다른 연결된 문자열((a, b), d)을 얻게 됩니다.
배열(배열s) - 배열은 숫자 키가 있는 개체입니다. V8 엔진에서 대용량 데이터를 저장할 때 널리 사용됩니다. 사전과 같은 키-값 쌍이 있는 객체는 배열을 사용하여 구현됩니다.
일반적인 JavaScript 객체는 두 가지 배열 유형, 즉- 명명된 속성과
- 숫자 요소 중 하나에 저장될 수 있습니다.
지도 - 개체 유형과 해당 구조를 설명하는 데 사용되는 개체입니다. 예를 들어, 맵은 객체 속성
객체 그룹에 빠르게 액세스할 수 있도록 객체의 구조를 설명하는 데 사용됩니다. 각 로컬 객체 그룹은 상호 연관된 객체 세트로 구성됩니다. 예를 들어 DOM 하위 트리에서 각 노드는 연관 그래프를 형성하는 상위 요소, 다음 하위 요소 및 다음 형제 요소에 액세스할 수 있습니다. 기본 요소는 JavaScript 힙에 표시되지 않습니다. 따라서 래핑 개체가 생성되는 동안 크기가 0이 됩니다.각 래퍼 개체에는 이러한 로컬 개체에 대한 작업을 전달하는 데 사용되는 로컬 개체에 대한 참조가 있습니다. 이러한 로컬 개체에는 래핑된 개체에 대한 참조도 있습니다. 그러나 이는 복구할 수 없는 루프를 생성하지 않습니다. GC는 더 이상 래핑된 개체에 대한 참조가 없는 로컬 개체를 식별하고 해제할 만큼 똑똑합니다. 그러나 래퍼 개체가 해제되지 않으면 모든 개체 그룹 및 관련 래퍼 개체가 유지됩니다.
전제 조건 및 유용한 팁
Chrome 작업 관리자
참고: 메모리 프로파일링을 위해 Chrome을 사용할 때는 깔끔한 테스트 환경을 설정하는 것이 가장 좋습니다.
Chrome의 메모리 관리자를 열고, 메모리 필드를 관찰하고, 페이지에서 관련 작업을 수행하면 이 작업으로 인해 페이지가 많은 메모리를 차지하게 되는지 빠르게 확인할 수 있습니다. Chrome 메뉴 > 도구에서 또는 Shift + Esc를 눌러 메모리 관리자를 찾을 수 있습니다.
열고 헤더를 마우스 오른쪽 버튼으로 클릭하고 JavasScript에서 사용하는 메모리 옵션을 선택합니다.
DevTools 타임라인을 통해 메모리 문제 찾기
문제 해결의 첫 번째 단계는 문제가 존재한다는 것을 증명하는 것입니다. 이를 위해서는 문제의 기준 척도 역할을 하는 재현 가능한 테스트를 만들어야 합니다. 재현 가능한 절차가 없으면 문제를 안정적으로 측정할 수 없습니다. 즉, 비교를 위한 기준선이 없으면 어떤 변경 사항으로 인해 문제가 발생했는지 알 수 없습니다.
타임라인 패널은 프로그램에 문제가 있을 때 발견하는 데 매우 유용합니다. 웹 애플리케이션이나 웹사이트가 로드되고 상호 작용하는 순간을 보여줍니다. 모든 이벤트: 리소스 로드부터 JavaScript 디코딩, 스타일 계산, 가비지 수집 일시 중지 및 페이지 다시 그리기까지. 모두 타임라인에 표시됩니다.
메모리 문제를 분석할 때 타임라인 패널의 메모리 보기를 사용하여 다음을 관찰할 수 있습니다.
- 사용된 총 메모리 – 메모리 사용량이 증가했습니다. ?
- DOM 노드 수
- 문서 수(문서)
- 등록 수 이벤트 리스너
메모리 분석 중 메모리 찾기에 대한 추가 정보 누출에 대해서는 Zack Grossbart의 Chrome DevTools를 사용한 메모리 프로파일링을 참조하세요
문제의 존재 증명가장 먼저 해야 할 일은 문제의 원인이라고 생각되는 것이 무엇인지 찾는 것입니다. 메모리 누수에 대한 몇 가지 조치. 이는 마우스 오버, 클릭 또는 페이지 성능 저하를 일으킬 수 있는 기타 상호 작용을 포함하여 페이지에서 발생하는 모든 이벤트일 수 있습니다.
타임라인 패널(Ctrl+E 또는 Cmd+E)에서 녹화를 시작한 다음 테스트하려는 작업을 수행하세요. 가비지 수집을 강제하려면 패널에서 휴지통 아이콘(
)을 클릭하세요.다음은 일부 포인트가 가비지 수집되지 않는 메모리 누수의 예입니다.
일부 반복 테스트 후에 들쭉날쭉한 그래픽( 메모리 패널 위)은 프로그램에 수명이 짧은 개체가 많이 있음을 나타냅니다. 그리고 일련의 작업을 수행해도 메모리가 일정 범위 내로 유지되지 않고, DOM 노드 수가 처음의 수로 돌아오지 않는다면 메모리 누수를 의심해볼 수 있습니다.
메모리 문제를 식별한 후에는
프로필 패널 프로파일러의 힙 분석기를 사용하여 문제를 찾을 수 있습니다. 문제의 근원.
예시: 타임라인을 통해 기억력 문제를 효과적으로연습분석하는 데 도움이 되는 기억력 성장 예시를 시도해 보세요.
메모리 재활용기메모리 수집기(V8의 것과 유사)는 어떤 개체가 라이브인지, 어떤 개체가 죽은(쓰레기)으로 간주되는 개체는 참조할 수 없습니다(unr각각가능).
가비지 컬렉션(GC)이 JavaScript 실행의 논리 오류로 인해 가비지 개체를 수집하지 못하는 경우 해당 가비지 개체는 더 이상 재활용할 수 없습니다. 이와 같은 상황은 결국 애플리케이션을 점점 더 느리게 만들 것입니다.
예를 들어 코드를 작성할 때 일부변수와 이벤트 리스너는 더 이상 사용되지 않지만 일부 코드에서는 여전히 참조됩니다. 참조가 여전히 존재하는 한 참조된 개체는 GC에서 올바르게 재활용될 수 없습니다.
애플리케이션이 실행되는 동안 일부 DOM 개체가 업데이트/제거되었을 수 있습니다. DOM 개체를 참조하는 변수를 확인하고null 설정하세요. . 다른 개체(또는 다른 DOM 요소)를 참조할 수 있는 개체 속성을 확인하세요. 커질 수 있는 변수 캐시를 지켜보세요.
힙 분석기
스냅샷 찍기
프로필 패널에서 힙 스냅샷 찍기를 선택하고 시작을 클릭하거나 Cmd를 누르세요. + E 또는 Ctrl + E:
스냅샷은 처음에 렌더러 프로세스 메모리에 저장됩니다. 요청 시 DevTools로 가져오며 스냅샷 버튼을 클릭하면 볼 수 있습니다. 스냅샷이 DevTools에 로드되어 표시되면 스냅샷 제목 아래의 숫자는 도달 가능한 JavaScript 객체가 차지하는 총 메모리 양을 표시합니다.
예: 타임라인 패널에서 메모리 사용량을 모니터링하려면 action 예에서 가비지 수집을 시도해 보세요.
스냅샷 지우기
모든 스냅샷을 지우려면 지우기 모두 버튼 아이콘 ()을 클릭하세요.
참고: DevTools 창을 닫아도 수집된 스냅샷은 렌더링 메모리에서 삭제되지 않습니다. DevTools를 다시 열면 이전 스냅샷 목록이 그대로 남아 있습니다.
앞서 언급한 내용을 기억하세요. 스냅샷을 찍을 때 DevTools에서 강제로 GC를 수행할 수 있습니다. 스냅샷을 찍으면 자동으로 GC가 실행됩니다. 타임라인에서 휴지통(가비지 수집) 버튼()을 클릭하면 가비지 수집을 쉽게 수행할 수 있습니다.
예: 분산된 개체를 시도하고 힙 프로파일러로 분석합니다. (객체) 아이템의 컬렉션을 볼 수 있습니다.
스냅샷 보기 전환
스냅샷은 다양한 작업에 따라 보기를 전환할 수 있습니다. 그림의 선택 상자를 통해 전환할 수 있습니다.
다음은 세 가지 기본 보기입니다.
요약 (요약) - 생성자를 통해 이름 분류로 객체 표시
Containment - 힙 콘텐츠를 감지하는 데 사용할 수 있습니다.
-
도미네이터설정
팅 패널에서 보기를 열 수 있으며, 메모리 성장 지점을 찾는 데 사용할 수 있습니다.
객체의 속성과 속성값은 종류가 다르며 자동으로 색상으로 구분됩니다. 각 속성은 다음 네 가지 중 하나입니다.
a:property
-의
이름으로 색인이 생성된 일반 속성으로 .( 점 )- 연산자
- 또는 [](대괄호) 참조(예: ["foo bar"];
a:context var - 함수 내 속성, 함수 컨텍스트 내 name Quote;
a:system prop - JavaScript VM에 의해 추가된 속성이며 JavaScript 코드로 액세스할 수 없습니다.
이라는 개체에 해당하는 JavaScript 유형이 없습니다. 이는 JavaScript VM 개체 시스템에 내장되어 있습니다. V8은 대부분의 내장 객체와 사용자 JS 객체를 동일한 힙에 배치합니다. 하지만 그것들은 V8의 내부 개체일 뿐입니다.
세부정보 보기
요약 보기(요약 보기)System
기본적으로 요약 보기에 표시되며 총 개체 수를 표시하고 확장할 수 있는 스냅샷을 엽니다. 특정 콘텐츠 표시: 처음에는 요약 보기에서 스냅샷이 열리고 표시
첫 번째 수준은 "전체" 행이며 다음과 같이 표시됩니다.
-
생성자(생성자) 는 모든 것을 의미합니다. 이 생성자
에 의해 생성된 객체의 인스턴스 수는 객체 Count 열
- 에 표시됩니다.
Shallow size 열에는 해당 생성자
에 의해 생성된 객체의 얕은 크기(메모리를 직접 점유)의 총 개수가 표시됩니다. 해당 객체가 차지하는 최대 메모리를 나타내는 열
Distance객체에서 GC 루트까지의 최단 거리를 나타내는 열
전체 행을 확장하면 모든 개체 인스턴스가 표시됩니다. 각 인스턴스가 차지하는 직접 메모리와 총 메모리가 그에 따라 표시됩니다. @ 기호 뒤의 숫자는 개체의 고유 ID이며 이를 사용하면 개체별로 서로 다른 스냅샷을 비교할 수 있습니다.
예: 개요 보기 사용 방법을 알아보려면 이 예(새 탭에서 열림)를 시도해 보세요.
노란색 객체는 JavaScript에서 참조하는 반면, 빨간색 객체는 노란색 배경색의 분리된 노드에서 참조한다는 점을 기억하세요.
비교 보기
이 보기는 서로 다른 스냅샷을 비교하여 스냅샷 간의 차이점을 찾고, 메모리 누수가 있는 개체를 찾는 데 사용됩니다. 애플리케이션의 특정 작업으로 인해 누출이 발생하지 않음을 증명하려면(예: 문서를 연 다음 닫는 등의 한 쌍의 작업과 실행 취소 작업이 누출을 유발하지 않음) 다음 단계를 시도해 볼 수 있습니다.
작업 전에 힙 스냅샷을 찍습니다.
작업을 수행합니다(누출을 일으킬 것으로 생각되는 작업을 수행합니다). >
이전 작업을 취소하고(이전 작업을 되돌리고 여러 번 반복) - 두 번째 스냅샷을 찍고 컨트롤 뷰로 전환합니다. 스냅샷 1로.
- 비교 보기에서는 두 스냅샷의 차이점이 표시됩니다. 일반 카테고리를 펼치면 추가 및 삭제된 개체가 표시됩니다.
예시: 사용 방법을 알아보려면 예시(새 탭에서 열기)를 시도해 보세요. 메모리 누수를 찾아보세요.
포함 뷰
컨트롤 뷰는 애플리케이션 객체 구조의 "조감도"라고 할 수 있습니다. 이를 통해 JavaScript 개체와 마찬가지로 VM 개체 내부의 함수 내부를 볼 수 있어 애플리케이션의 메모리 사용량에 대한 매우 낮은 수준의 보기를 제공합니다.
이 보기는 여러 진입점을 제공합니다.
- DOMWindow 개체
- 이러한 개체는 JavaScript 코드에 대한 "전역" 개체입니다.
- GC 루트
- VM 가비지 수집기의 실제 GC 루트
- 네이티브 개체
- 개체 찾아보기 DOM 노드, CSS 규칙과 같은 자동 작업을 수행하기 위해 JavaScript 가상 머신에 "푸시"됩니다(자세한 내용은 다음 섹션에서 소개됩니다.)
다음 그림은 일반적인 컨트롤 뷰:
예: 컨트롤 뷰를 사용하여
클로저내부를 확인하는 방법을 보려면 예제(새 탭에서 열기)를 시도해 보세요. 그리고 이벤트 처리.
클로저 팁함수 이름을 지정하면 스냅샷에서 클로저 함수를 구별하는 데 도움이 될 수 있습니다. 예를 들어 다음 예에서는 함수 이름을 지정하지 않습니다.
function createLargeClosure() { var largeStr = new Array(1000000).join('x'); var lC = function() { // this is NOT a named function return largeStr; }; return lC; }