메모리 용어

메긴 키어니
메긴 키어니

이 섹션에서는 메모리 분석에 사용되는 일반적인 용어를 설명하며 여러 언어의 다양한 메모리 프로파일링 도구에 적용할 수 있습니다.

여기에서 설명하는 용어와 개념은 Chrome DevTools 힙 프로파일러를 참조하세요. Java, .NET 또는 다른 메모리 프로파일러를 사용해 본 적이 있다면 이 내용을 복습해 보세요.

객체 크기

메모리를 기본 유형 (예: 숫자, 문자열)과 객체 (연관 배열)로 구성된 그래프라고 생각하면 됩니다. 이 차트는 다음과 같이 상호 연결된 여러 점의 그래프로 시각적으로 표현될 수 있습니다.

메모리의 시각적 표현

객체는 두 가지 방식으로 메모리를 보유할 수 있습니다.

  • 객체 자체에서 직접 호출
  • 암시적으로 다른 객체에 관한 참조를 보유함으로써 이러한 객체가 가비지 컬렉터 (줄여서 GC)에 의해 자동으로 삭제되는 것을 방지합니다.

DevTools에서 힙 프로파일러를 사용할 때('프로필'에서 발견된 메모리 문제를 조사하는 도구) 사용할 때 몇 가지 다른 정보 열을 보게 될 것입니다. 그 중에서 가장 눈에 띄는 두 가지는 Shallow SizeRetained Size이지만, 이들이 의미하는 바는 무엇일까요?

얕은 유지 및 유지 사이즈

Shallow Size(얕은 크기)

객체 자체가 보유한 메모리의 크기입니다.

일반적인 자바스크립트 객체에는 설명 및 즉시 값을 저장하기 위한 일부 메모리가 있습니다. 일반적으로 배열과 문자열만 상당히 얕은 크기를 가질 수 있습니다. 그러나 문자열과 외부 배열의 경우 렌더기 메모리에 기본 저장소가 있어 JavaScript 힙에 작은 래퍼 객체만 노출됩니다.

렌더기 메모리는 검사된 페이지가 렌더링되는 프로세스의 모든 메모리입니다. 네이티브 메모리 + 페이지의 JS 힙 메모리 + 페이지에서 시작한 모든 전용 작업자의 JS 힙 메모리입니다. 그럼에도 불구하고 작은 객체도 자동 가비지 컬렉션 프로세스에 의해 다른 객체가 삭제되지 않도록 하여 많은 양의 메모리를 간접적으로 보유할 수 있습니다.

유지된 크기

객체 자체가 GC roots에서 연결할 수 없게 된 종속 객체와 함께 삭제되면 해제되는 메모리의 크기입니다.

GC 루트는 네이티브 코드에서 V8 외부의 JavaScript 객체를 참조할 때 생성되는 핸들로 구성됩니다 (로컬 또는 전역). 이러한 핸들은 모두 GC roots > Handle scopeGC roots > Global processes의 힙 스냅샷 내에서 찾을 수 있습니다. 브라우저 구현을 자세히 다루지 않고 이 문서에서 핸들을 설명하면 혼란스러울 수 있습니다. GC 루트와 핸들은 모두 개발자가 신경 쓸 필요가 없습니다.

많은 내부 GC 루트가 있지만 대부분은 사용자가 관심을 가지지 않습니다. 애플리케이션 관점에서는 다음과 같은 종류의 루트가 있습니다.

  • Window 전역 객체 (각 iframe) 힙 스냅샷에는 거리 필드가 있는데, 이는 창에서 가장 짧은 보존 경로에 있는 속성 참조의 수입니다.
  • 문서를 순회하여 도달할 수 있는 모든 네이티브 DOM 노드로 구성된 문서 DOM 트리. 이들 모두에 JS 래퍼가 있는 것은 아니지만 래퍼가 있으면 문서가 활성 상태인 동안 래퍼가 활성화됩니다.
  • 디버거 컨텍스트 및 DevTools 콘솔에 의해 객체가 보관되는 경우도 있습니다 (예: 콘솔 평가 후). 콘솔을 비우고 디버거 내 활성 중단점이 없는 상태로 힙 스냅샷을 만듭니다.

메모리 그래프는 루트로 시작하며, 브라우저의 window 객체 또는 Node.js 모듈의 Global 객체일 수 있습니다. 이 루트 객체의 GC 처리 방식은 개발자가 제어할 수 없습니다.

루트 객체는 제어할 수 없음

루트에서 연결할 수 없는 것은 무엇이든 GC를 가져옵니다.

객체 보존 트리

힙은 상호 연결된 객체의 네트워크입니다. 수학에서는 이 구조를 그래프 또는 메모리 그래프라고 부릅니다. 그래프는 에지를 통해 연결된 노드로 구성되며, 두 노드 모두 라벨이 지정됩니다.

  • 노드 (또는 객체)는 노드를 빌드하는 데 사용된 생성자 함수의 이름을 사용하여 라벨이 지정됩니다.
  • 에지속성의 이름을 사용하여 라벨이 지정됩니다.

힙 프로파일러를 사용하여 프로필을 기록하는 방법을 알아보세요. 아래의 힙 프로파일러 기록에서 눈에 띄는 몇 가지로는 GC 루트로부터의 거리와 같은 거리가 있습니다. 동일한 유형의 거의 모든 객체가 같은 거리에 있고 몇몇은 더 큰 거리에 있다면 조사할만한 가치가 있습니다.

루트로부터의 거리

도미네이터

도미네이터 객체는 트리 구조로 구성됩니다. 각 객체에 정확히 하나의 도미네이터가 있기 때문입니다. 객체의 도미네이터에는 자신이 지배하는 객체에 대한 직접 참조가 없을 수 있습니다. 즉, 도미네이터의 트리는 그래프의 스패닝 트리가 아닙니다.

아래 다이어그램에서 각 항목의 의미는 다음과 같습니다.

  • 노드 1이 노드 2를 지배합니다.
  • 노드 2는 노드 3, 4, 6을 지배합니다.
  • 노드 3은 노드 5를 지배합니다.
  • 노드 5가 노드 8을 지배합니다.
  • 노드 6은 노드 7을 지배합니다.

도미네이터 트리 구조

아래 예에서 노드 #3#10의 도미네이터이지만, #7는 GC에서 #10으로 이어지는 모든 간단한 경로에도 존재합니다. 따라서 루트에서 객체 A로 이어지는 모든 단순 경로에 객체 B가 존재하면 객체 B는 객체 A의 도미네이터입니다.

도미네이터 애니메이션 삽화

V8 사양

메모리를 프로파일링할 때 힙 스냅샷이 특정 방식으로 보이는 이유를 이해하면 도움이 됩니다. 이 섹션에서는 특히 V8 자바스크립트 가상 머신 (V8 VM 또는 VM)에 해당하는 일부 메모리 관련 주제를 설명합니다.

자바스크립트 객체 표현

세 가지 기본 유형이 있습니다.

  • 숫자 (예: 3.14159..)을 입력합니다.
  • 불리언 (true 또는 false)
  • 문자열 (예: '베르너 하이젠베르크')

이러한 값은 다른 값을 참조할 수 없으며 항상 리프 또는 종료 노드입니다.

숫자는 다음 중 하나로 저장할 수 있습니다.

  • 작은 정수 (SMI)라고 하는 즉각 31비트 정수 값
  • 힙 숫자라고 하는 힙 객체. 힙 번호는 SMI 형식에 맞지 않는 값(예: double)을 저장하거나 값을 박싱해야 할 때(예: 속성을 설정)하는 데 사용됩니다.

문자열은 다음 중 하나에 저장할 수 있습니다.

  • VM 힙
  • 렌더러의 메모리에 저장됩니다. 래퍼 객체가 생성되고 외부 저장소에 액세스하는 데 사용됩니다. 예를 들어 웹에서 수신한 스크립트 소스와 기타 콘텐츠는 VM 힙에 복사되지 않고 저장됩니다.

새 자바스크립트 객체의 메모리는 전용 자바스크립트 힙 (또는 VM 힙)에서 할당됩니다. 이러한 객체는 V8의 가비지 컬렉터에서 관리하므로 강력한 참조가 하나 이상 존재하는 한 활성 상태로 유지됩니다.

네이티브 객체는 자바스크립트 힙에 없는 모든 것을 의미합니다. 힙 객체와 달리 네이티브 객체는 전체 기간 동안 V8 가비지 컬렉터에서 관리하지 않으며 JavaScript 래퍼 객체를 사용하여 자바스크립트에서만 액세스할 수 있습니다.

Cons 문자열은 저장된 후 조인된 문자열 쌍으로 구성된 객체로, 연결의 결과입니다. cons string 콘텐츠의 조인은 필요한 경우에만 발생합니다. 예를 들어 조인된 문자열의 하위 문자열을 생성해야 하는 경우가 있습니다.

예를 들어 ab를 연결하면 연결 결과를 나타내는 문자열 (a, b)를 얻게 됩니다. 나중에 d를 해당 결과에 연결하면 또 다른 cons 문자열 ((a, b), d)이 생깁니다.

배열 - 배열은 숫자 키가 있는 객체입니다. V8 VM에서 대용량 데이터를 저장하기 위해 광범위하게 사용됩니다. 사전과 같이 사용되는 키-값 쌍의 집합은 배열에 의해 백업됩니다.

일반적인 JavaScript 객체는 저장에 사용되는 다음 두 가지 배열 유형 중 하나일 수 있습니다.

  • 이름이 지정된 속성
  • 숫자 요소

속성 수가 매우 적은 경우에는 자바스크립트 객체 자체에 내부적으로 저장할 수 있습니다.

Map: 객체의 종류와 그 레이아웃을 설명하는 객체 예를 들어 맵은 빠른 속성 액세스를 위해 암시적 객체 계층 구조를 설명하는 데 사용됩니다.

객체 그룹

각 네이티브 객체 그룹은 서로에 대한 상호 참조를 보유한 객체로 구성됩니다. 예를 들어 모든 노드에 상위 노드에 대한 링크와 다음 하위 및 다음 동위 노드에 대한 링크가 있어서 연결된 그래프를 형성하는 DOM 하위 트리가 있다고 가정해 보겠습니다. 네이티브 객체는 자바스크립트 힙에 표시되지 않으므로 크기가 0입니다. 대신 래퍼 객체가 생성됩니다.

각 래퍼 객체는 명령어를 리디렉션하기 위해 상응하는 네이티브 객체에 관한 참조를 보유합니다. 또한 객체 그룹은 래퍼 객체를 보유합니다. 그러나 GC는 래퍼가 더 이상 참조되지 않는 객체 그룹을 해제할 만큼 지능적이기 때문에 수집 불가능한 주기를 만들지는 않습니다. 그러나 단일 래퍼를 해제하는 것을 잊으면 전체 그룹 및 연결된 래퍼가 보류됩니다.