힙 스냅샷 기록

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

메모리 > 프로필 > 힙 스냅샷으로 힙 스냅샷을 기록하고 메모리 누수를 찾는 방법을 알아보세요.

힙 프로파일러는 페이지의 JavaScript 객체와 관련 DOM 노드별로 메모리 분포를 보여줍니다. 이 도구를 사용하여 JS 힙 스냅샷을 찍고, 메모리 그래프를 분석하고, 스냅샷을 비교하고, 메모리 누수를 찾아보세요. 자세한 내용은 객체 보존 트리를 참조하세요.

스냅샷 만들기

힙 스냅샷을 찍으려면 다음 단계를 따르세요.

  1. 프로파일링하려는 페이지에서 DevTools를 열고 Memory 패널로 이동합니다.
  2. radio_button_checked 힙 스냅샷 프로파일링 유형을 선택한 다음, JavaScript VM 인스턴스를 선택하고, 스냅샷 만들기를 클릭합니다.

선택한 프로파일링 유형 및 JavaScript VM 인스턴스

메모리 패널에서 스냅샷을 로드하고 파싱하면 힙 스냅샷 섹션의 스냅샷 제목 아래에 연결 가능한 JavaScript 객체의 총 크기가 표시됩니다.

연결 가능한 객체의 총 크기입니다.

스냅샷에는 전역 객체에서 연결할 수 있는 메모리 그래프의 객체만 표시됩니다. 스냅샷 생성은 항상 가비지 컬렉션으로 시작합니다.

분산형 Item 객체의 힙 스냅샷

스냅샷 지우기

모든 스냅샷을 삭제하려면 차단 모든 프로필 지우기를 클릭합니다.

모든 프로필 삭제를 탭합니다.

스냅샷 보기

다양한 관점의 스냅샷을 다양한 목적으로 검사하려면 상단의 드롭다운 메뉴에서 뷰 중 하나를 선택합니다.

보기 콘텐츠 목적
요약 생성자 이름별로 그룹화된 객체입니다. 이를 사용하여 유형을 기준으로 객체와 객체의 메모리 사용량을 추적하세요. DOM 누수를 추적하는 데 유용합니다.
비교 두 스냅샷의 차이점 이 도구를 사용하면 작업 전후의 스냅샷을 두 개 (또는 그 이상) 비교할 수 있습니다. 확보된 메모리와 참조 카운트의 델타를 검사하여 메모리 누수의 존재와 원인을 확인합니다.
억제 힙 콘텐츠 객체 구조를 더 잘 보여주고 전역 네임스페이스 (창)에서 참조되는 객체를 분석하여 보관 중인 객체를 찾는 데 도움을 줍니다. 이를 사용하여 클로저를 분석하고 객체를 낮은 수준에서 자세히 살펴볼 수 있습니다.
통계 메모리 할당 원형 차트 코드, 문자열, JS 배열, 유형이 지정된 배열, 시스템 객체에 할당된 메모리 부분의 실제 크기를 확인합니다.

상단의 드롭다운 메뉴에서 선택한 요약 보기

요약 뷰

처음에는 열에 생성자를 나열하는 Summary 뷰에서 힙 스냅샷이 열립니다. 생성자를 확장하여 생성자가 인스턴스화한 객체를 확인할 수 있습니다.

확장된 생성자가 있는 요약 뷰

관련 없는 생성자를 필터링하려면 Summary 보기 상단의 Class 필터에 검사하려는 이름을 입력합니다.

생성자 이름 옆의 숫자는 생성자로 만든 객체의 총 수를 나타냅니다. 요약 보기에는 다음 열도 표시됩니다.

  • Distance는 최단 거리의 단순한 노드 경로를 사용하여 루트까지의 거리를 표시합니다.
  • Shallow Size는 특정 생성자에 의해 생성된 모든 객체의 Shallow Size 총합을 표시합니다. Shallow Size란 객체 자체가 보유한 메모리의 크기입니다. 일반적으로 배열과 문자열은 더 큰 Shallow Size를 가집니다. 객체 크기도 참조하세요.
  • Retained 크기는 동일한 객체 집합 중에서 최대 보존 크기를 보여줍니다. 보관된 크기는 객체를 삭제하고 종속 항목에 더 이상 연결할 수 없도록 하여 확보할 수 있는 메모리의 크기입니다. 객체 크기도 참조하세요.

생성자를 펼치면 요약 뷰에는 생성자의 모든 인스턴스가 표시됩니다. 각 인스턴스는 해당 열에서 Shallow Size와 보존된 크기의 분석을 받습니다. @ 문자 다음에 오는 숫자는 객체의 고유 ID입니다. 객체별로 힙 스냅샷을 비교할 수 있습니다.

요약의 특별 항목

요약 보기는 생성자별 그룹화 외에도 다음을 기준으로 객체를 그룹화합니다.

  • Array 또는 Object와 같은 내장 함수.
  • 코드에 정의한 함수입니다.
  • 생성자를 기반으로 하지 않는 특수 카테고리입니다.

생성자 항목.

(array)

이 카테고리에는 JavaScript에 표시되는 객체에 직접 대응하지 않는 다양한 내부 배열 같은 객체가 포함됩니다.

예를 들어 JavaScript Array 객체의 콘텐츠는 크기를 더 쉽게 조절할 수 있도록 (object elements)[]라는 보조 내부 객체에 저장됩니다. 마찬가지로 JavaScript 객체의 명명된 속성은 (object properties)[]라는 보조 내부 객체에 저장되는 경우가 많습니다. 이 객체는 (array) 카테고리에도 나열됩니다.

(compiled code)

이 카테고리에는 V8에서 자바스크립트 또는 WebAssembly로 정의된 함수를 실행하는 데 필요한 내부 데이터가 포함됩니다. 각 함수는 작고 느린 함수부터 크고 빠른 함수까지 다양한 방식으로 표현할 수 있습니다.

V8은 이 카테고리의 메모리 사용량을 자동으로 관리합니다. 함수가 여러 번 실행되는 경우 V8은 더 빠르게 실행할 수 있도록 해당 함수에 더 많은 메모리를 사용합니다. 함수가 한동안 실행되지 않으면 V8이 해당 함수의 내부 데이터를 지울 수 있습니다.

(concatenated string)

V8이 두 문자열을 연결할 때(예: JavaScript + 연산자 사용) 내부적으로 결과를 Rope 데이터 구조라고도 하는 '연결된 문자열'로 나타낼 수 있습니다.

두 소스 문자열의 모든 문자를 새 문자열로 복사하는 대신 V8은 두 개의 소스 문자열을 가리키는 firstsecond라는 내부 필드가 있는 작은 객체를 할당합니다. 이를 통해 V8은 시간과 메모리를 절약할 수 있습니다. JavaScript 코드의 관점에서는 일반 문자열이며 다른 문자열처럼 동작합니다.

InternalNode

이 카테고리는 Blink로 정의된 C++ 객체와 같이 V8 외부에 할당된 객체를 나타냅니다.

C++ 클래스 이름을 보려면 Chrome for Testing을 사용하여 다음 단계를 따르세요.

  1. DevTools를 열고 settings 설정 > 실험 > check_box 힙 스냅샷에 내부 요소를 노출하는 옵션 표시를 사용 설정합니다.
  2. 메모리 패널을 열고 radio_button_checked 힙 스냅샷을 선택한 후 radio_button_checked 내부 기능 노출 (추가 구현별 세부정보 포함)을 사용 설정합니다.
  3. InternalNode에서 많은 양의 메모리를 유지하게 하는 문제를 재현합니다.
  4. 힙 스냅샷을 찍습니다. 이 스냅샷에서 객체에는 InternalNode 대신 C++ 클래스 이름이 있습니다.
(object shape)

V8의 빠른 속성에 설명된 대로 V8은 숨겨진 클래스 (또는 도형)를 추적하므로 속성이 동일한 여러 객체를 효율적으로 표현할 수 있습니다. 이 카테고리에는 JavaScript Map와 관련 없는 system / Map라는 숨겨진 클래스와 관련 데이터가 포함됩니다.

(sliced string)

JavaScript 코드에서 String.prototype.substring()를 호출하는 경우와 같이 V8에서 하위 문자열을 사용해야 하는 경우 V8은 원래 문자열에서 모든 관련 문자를 복사하는 대신 슬라이스 문자열 객체를 할당하도록 선택할 수 있습니다. 이 새 객체는 원본 문자열에 대한 포인터를 포함하며 원본 문자열에서 사용할 문자 범위를 설명합니다.

JavaScript 코드의 관점에서는 일반 문자열이며 다른 문자열처럼 동작합니다. 슬라이스된 문자열이 많은 양의 메모리를 보존하는 경우 프로그램에서 문제 2869를 트리거하여 슬라이스된 문자열을 '평면화'하는 의도적인 조치를 취할 수 있습니다.

system / Context

system / Context 유형의 내부 객체에는 중첩 함수가 액세스할 수 있는 자바스크립트 범위인 클로저의 로컬 변수가 포함되어 있습니다.

모든 함수 인스턴스에는 실행되는 Context의 내부 포인터가 포함되어 있으므로 이러한 변수에 액세스할 수 있습니다. Context 객체는 JavaScript에서 직접 표시되지 않지만 개발자가 직접 제어할 수 있습니다.

(system)

이 카테고리에는 아직 더 의미 있는 방식으로 분류되지 않은 다양한 내부 객체가 포함되어 있습니다.

비교 보기

Comparison 뷰를 사용하면 여러 스냅샷을 서로 비교하여 누수된 객체를 찾을 수 있습니다. 예를 들어 문서를 열고 닫는 등 작업을 수행했다가 되돌리는 경우에도 추가 객체가 남겨져서는 안 됩니다.

특정 작업으로 인해 누수가 발생하지 않는지 확인하려면 다음 안내를 따르세요.

  1. 작업을 수행하기 전에 힙 스냅샷을 찍습니다.
  2. 연산을 실행합니다. 즉, 누수를 야기할 수 있다고 생각하는 방식으로 페이지와 상호작용합니다.
  3. 역방향 연산을 수행합니다. 즉, 반대 상호 작용을 하고 이를 몇 번 반복합니다.
  4. 두 번째 힙 스냅샷을 만들고 뷰를 Comparison으로 변경하여 Snapshot 1과 비교합니다.

Comparison 뷰는 두 스냅샷의 차이를 보여줍니다. total 항목을 확장하면 추가되거나 삭제된 객체 인스턴스가 표시됩니다.

스냅샷 1과 비교:

격리 보기

Containment 뷰는 애플리케이션의 객체 구조를 '조감도'로 보여줍니다. 이를 통해 함수 클로저 내부를 들여다볼 수 있고, 자바스크립트 객체를 구성하는 VM 내부 객체를 관찰할 수 있으며, 애플리케이션이 얼마나 많은 메모리를 사용하는지 아주 낮은 수준에서 파악할 수 있습니다.

이 뷰는 여러 진입점을 제공합니다.

  • DOMWindow 객체의 사용자 인증 정보를 전달합니다. 자바스크립트 코드의 전역 객체
  • GC 루트. VM의 가비지 컬렉터가 사용하는 GC 루트입니다. GC 루트는 기본 제공 객체 맵, 기호 테이블, VM 스레드 스택, 컴파일 캐시, 핸들 범위 및 전역 핸들로 구성될 수 있습니다.
  • 네이티브 객체. 자동화를 허용하기 위해 자바스크립트 가상 머신 내부로 '푸시'되는 브라우저 객체(예: DOM 노드 및 CSS 규칙)

격리 보기

자문 서비스 섹션

Memory 패널 하단의 Retainers 섹션에는 뷰에서 선택한 객체를 가리키는 객체가 표시됩니다.

자문 서비스 섹션

이 예에서 선택된 문자열은 Item 인스턴스의 x 속성에 의해 유지됩니다.

특정 객체 찾기

수집된 힙에서 객체를 찾으려면 Ctrl + F를 사용하여 검색하고 객체 ID를 입력합니다.

클로저를 구별하기 위한 함수 이름 지정

스냅샷에서 클로저를 구별할 수 있도록 함수 이름을 지정하면 많은 도움이 됩니다.

예를 들어 다음 코드는 이름이 지정된 함수를 사용하지 않습니다.

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

이 예에서는 다음을 실행합니다.

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

클로저의 이름이 지정된 함수

DOM 누수 발견

힙 프로파일러는 브라우저 네이티브 객체 (DOM 노드 및 CSS 규칙)와 JavaScript 객체 간의 양방향 종속성을 반영할 수 있습니다. 이는 떠다니는 분리된 DOM 하위 트리로 인해 눈에 보이지 않는 누수를 발견하는 데 도움이 됩니다.

DOM 유출은 생각보다 클 수 있습니다. 다음 예를 참고하세요. #tree 가비지는 언제 수집되나요?

  var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW #tree can be garbage collected

#leaf는 상위 요소 (parentNode)의 참조를 유지하며 #tree까지 재귀적으로 유지합니다. 따라서 leafRef가 무효화될 때만 #tree 아래의 전체 트리가 GC의 후보가 됩니다.

DOM 하위 트리