메모리 용어

Meggin Kearney
Meggin Kearney

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

여기에 설명된 용어와 개념은 Chrome DevTools 힙 프로파일러에 적용됩니다. Java, .NET 또는 기타 메모리 프로파일러를 사용해 본 적이 있다면 이 내용을 복습하는 데 도움이 될 수 있습니다.

객체 크기

메모리를 기본 유형 (예: 숫자 및 문자열)과 객체 (연결 배열)가 있는 그래프로 생각해 보세요. 다음과 같이 여러 개의 연결된 점으로 구성된 그래프로 시각적으로 표현될 수 있습니다.

메모리의 시각적 표현입니다.

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

  • 객체 자체에서 직접
  • 다른 객체에 대한 참조를 보유하여 가비지 컬렉터 (줄여서 GC)에 의해 이러한 객체가 자동으로 삭제되지 않도록 암시적으로

DevTools의 힙 프로파일러 (메모리 패널에 있는 메모리 문제를 조사하는 도구)를 사용하면 여러 개의 정보 열을 확인하게 됩니다. 눈에 띄는 두 가지는 Shallow SizeRetained Size입니다. 이 두 가지는 무엇을 나타내나요?

메모리 패널의 Shallow 및 Retained Size 열

얕은 크기

객체 자체에 보관되는 메모리의 크기입니다.

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

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

유지된 크기

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

GC 루트는 V8 외부의 네이티브 코드에서 JavaScript 객체를 참조할 때 생성되는 핸들 (로컬 또는 전역)로 구성됩니다. 이러한 모든 핸들은 힙 스냅샷 내의 GC 루트 > 핸들 범위GC 루트 > 전역 핸들에서 확인할 수 있습니다. 브라우저 구현의 세부정보를 자세히 살펴보지 않고 이 문서에서 핸들을 설명하면 혼란스러울 수 있습니다. 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로의 모든 간단한 경로에 존재합니다. 따라서 객체 B가 루트에서 객체 A로의 모든 단순 경로에 있으면 객체 B는 객체 A의 도미넌트입니다.

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

V8 세부정보

메모리를 프로파일링할 때는 힙 스냅샷이 특정 방식으로 표시되는 이유를 이해하는 것이 좋습니다. 이 섹션에서는 특히 V8 JavaScript 가상 머신 (V8 VM 또는 VM)에 해당하는 몇 가지 메모리 관련 주제를 설명합니다.

JavaScript 객체 표현

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

  • 숫자 (예: 3.14159..)
  • 불리언 (true 또는 false)
  • 문자열 (예: 'Werner Heisenberg')

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

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

  • 소수 정수 (SMIs)라고 하는 즉시 31비트 정수 값
  • 힙 객체(힙 번호라고 함) 힙 숫자는 double와 같이 SMI 형식에 맞지 않는 값을 저장하거나 값에 속성을 설정하는 등 값을 박스 처리해야 하는 경우에 사용됩니다.

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

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

새 JavaScript 객체의 메모리는 전용 JavaScript 힙 (또는 VM 힙)에서 할당됩니다. 이러한 객체는 V8의 가비지 컬렉터에 의해 관리되므로 이러한 객체에 대한 강력한 참조가 하나 이상 있는 한 활성 상태로 유지됩니다.

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

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

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

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

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

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

속성이 매우 적은 경우 JavaScript 객체 자체에 내부적으로 저장할 수 있습니다.

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

객체 그룹

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

각 래퍼 객체는 명령어를 리디렉션하기 위해 상응하는 네이티브 객체에 대한 참조를 보유합니다. 객체 그룹은 래퍼 객체를 보유합니다. 그러나 GC는 래퍼가 더 이상 참조되지 않는 객체 그룹을 해제할 만큼 똑똑하므로 수집 불가능한 사이클이 발생하지 않습니다. 하지만 단일 래퍼를 해제하지 않으면 전체 그룹과 연결된 래퍼가 유지됩니다.