Skip to content

V8

소스 코드: lib/v8.js

node:v8 모듈은 Node.js 바이너리에 내장된 V8 버전에 특정한 API를 노출합니다. 다음을 사용하여 접근할 수 있습니다:

js
const v8 = require('node:v8')

v8.cachedDataVersionTag()

추가된 버전: v8.0.0

V8 버전, 커맨드라인 플래그, 감지된 CPU 기능에서 파생된 버전 태그를 나타내는 정수를 반환합니다. 이것은 vm.Script cachedData 버퍼가 이 V8 인스턴스와 호환되는지 여부를 결정하는 데 유용합니다.

js
console.log(v8.cachedDataVersionTag()) // 3947234607
// v8.cachedDataVersionTag()에서 반환된 값은 V8 버전, 커맨드라인 플래그 및
// 감지된 CPU 기능에서 파생됩니다. 플래그가 토글될 때 값이 실제로 업데이트되는지 테스트합니다.
v8.setFlagsFromString('--allow_natives_syntax')
console.log(v8.cachedDataVersionTag()) // 183726201

v8.getHeapCodeStatistics()

추가된 버전: v12.8.0

힙의 코드와 해당 메타데이터에 대한 통계를 가져옵니다. V8 GetHeapCodeAndMetadataStatistics API를 참조하십시오. 다음 속성을 가진 객체를 반환합니다:

js
{
  code_and_metadata_size: 212208,
  bytecode_and_metadata_size: 161368,
  external_script_source_size: 1410794,
  cpu_profiler_metadata_size: 0,
}

v8.getHeapSnapshot([options])

[연혁]

버전변경 사항
v19.1.0힙 스냅샷을 구성하는 옵션 지원.
v11.13.0추가됨: v11.13.0
  • options <Object>

    • exposeInternals <boolean> 참이면 힙 스냅샷에 내부를 노출합니다. 기본값: false.
    • exposeNumericValues <boolean> 참이면 인위적인 필드에 숫자 값을 노출합니다. 기본값: false.
  • 반환값: <stream.Readable> V8 힙 스냅샷을 포함하는 읽기 가능한 스트림.

현재 V8 힙의 스냅샷을 생성하고 JSON 직렬화된 표현을 읽는 데 사용할 수 있는 읽기 가능한 스트림을 반환합니다. 이 JSON 스트림 형식은 Chrome DevTools와 같은 도구와 함께 사용하도록 고안되었습니다. JSON 스키마는 문서화되지 않았으며 V8 엔진에 특화되어 있습니다. 따라서 스키마는 V8 버전이 바뀔 때마다 변경될 수 있습니다.

힙 스냅샷을 생성하려면 스냅샷이 생성될 때 힙 크기의 약 두 배의 메모리가 필요합니다. 이로 인해 OOM 킬러가 프로세스를 종료할 위험이 있습니다.

스냅샷 생성은 동기식 작업이며 힙 크기에 따라 이벤트 루프를 차단합니다.

js
// 콘솔에 힙 스냅샷 출력
const v8 = require('node:v8')
const stream = v8.getHeapSnapshot()
stream.pipe(process.stdout)

v8.getHeapSpaceStatistics()

[연혁]

버전변경 사항
v7.5.032비트 부호 없는 정수 범위를 초과하는 값 지원.
v6.0.0추가됨: v6.0.0

V8 힙 공간, 즉 V8 힙을 구성하는 세그먼트에 대한 통계를 반환합니다. 힙 공간의 순서나 힙 공간의 가용성은 V8 GetHeapSpaceStatistics 함수를 통해 통계가 제공되고 V8 버전이 바뀔 때마다 변경될 수 있으므로 보장할 수 없습니다.

반환되는 값은 다음 속성을 포함하는 객체의 배열입니다.

json
[
  {
    "space_name": "new_space",
    "space_size": 2063872,
    "space_used_size": 951112,
    "space_available_size": 80824,
    "physical_space_size": 2063872
  },
  {
    "space_name": "old_space",
    "space_size": 3090560,
    "space_used_size": 2493792,
    "space_available_size": 0,
    "physical_space_size": 3090560
  },
  {
    "space_name": "code_space",
    "space_size": 1260160,
    "space_used_size": 644256,
    "space_available_size": 960,
    "physical_space_size": 1260160
  },
  {
    "space_name": "map_space",
    "space_size": 1094160,
    "space_used_size": 201608,
    "space_available_size": 0,
    "physical_space_size": 1094160
  },
  {
    "space_name": "large_object_space",
    "space_size": 0,
    "space_used_size": 0,
    "space_available_size": 1490980608,
    "physical_space_size": 0
  }
]

v8.getHeapStatistics()

[기록]

버전변경 사항
v7.5.032비트 부호 없는 정수 범위를 초과하는 값 지원.
v7.2.0malloced_memory, peak_malloced_memory, 및 does_zap_garbage 추가.
v1.0.0v1.0.0에 추가됨

다음 속성을 가진 객체를 반환합니다.

total_heap_size total_heap_size의 값은 V8이 힙에 할당한 바이트 수입니다. used_heap에 더 많은 메모리가 필요한 경우 이 값이 커질 수 있습니다.

total_heap_size_executable total_heap_size_executable의 값은 실행 가능한 코드를 포함할 수 있는 힙의 일부이며, 바이트 단위입니다. 여기에는 JIT 컴파일된 코드에서 사용되는 메모리와 실행 가능해야 하는 모든 메모리가 포함됩니다.

total_physical_size total_physical_size의 값은 V8 힙에서 사용하는 실제 물리적 메모리이며, 바이트 단위입니다. 이는 예약된 메모리보다는 커밋된(또는 사용 중인) 메모리 양입니다.

total_available_size total_available_size의 값은 V8 힙에서 사용할 수 있는 메모리의 바이트 수입니다. 이 값은 V8이 힙 제한을 초과하기 전에 사용할 수 있는 메모리 양을 나타냅니다.

used_heap_size used_heap_size의 값은 현재 V8의 JavaScript 객체에서 사용 중인 바이트 수입니다. 이는 실제로 사용 중인 메모리이며 할당되었지만 아직 사용되지 않은 메모리는 포함하지 않습니다.

heap_size_limit heap_size_limit 값은 V8 힙의 최대 크기이며, 바이트 단위입니다(시스템 리소스에 의해 결정되는 기본 제한 또는 --max_old_space_size 옵션에 전달된 값).

malloced_memory malloced_memory의 값은 V8에 의해 malloc을 통해 할당된 바이트 수입니다.

peak_malloced_memory peak_malloced_memory의 값은 프로세스 수명 동안 V8에 의해 malloc을 통해 할당된 최대 바이트 수입니다.

does_zap_garbage--zap_code_space 옵션이 활성화되었는지 여부를 나타내는 0/1 부울입니다. 이를 통해 V8은 힙 가비지를 비트 패턴으로 덮어씁니다. RSS 풋프린트(상주 집합 크기)는 모든 힙 페이지를 계속해서 터치하기 때문에 더 커지며, 운영 체제에서 스왑 아웃될 가능성이 줄어듭니다.

number_of_native_contexts native_context의 값은 현재 활성 상태인 최상위 컨텍스트의 수입니다. 시간이 지남에 따라 이 숫자가 증가하면 메모리 누수를 나타냅니다.

number_of_detached_contexts detached_context의 값은 분리되었지만 아직 가비지 수집되지 않은 컨텍스트의 수입니다. 이 숫자가 0이 아니면 잠재적인 메모리 누수를 나타냅니다.

total_global_handles_size total_global_handles_size의 값은 V8 전역 핸들의 총 메모리 크기입니다.

used_global_handles_size used_global_handles_size의 값은 V8 전역 핸들의 사용된 메모리 크기입니다.

external_memory external_memory의 값은 배열 버퍼와 외부 문자열의 메모리 크기입니다.

js
{
  total_heap_size: 7326976,
  total_heap_size_executable: 4194304,
  total_physical_size: 7326976,
  total_available_size: 1152656,
  used_heap_size: 3476208,
  heap_size_limit: 1535115264,
  malloced_memory: 16384,
  peak_malloced_memory: 1127496,
  does_zap_garbage: 0,
  number_of_native_contexts: 1,
  number_of_detached_contexts: 0,
  total_global_handles_size: 8192,
  used_global_handles_size: 3296,
  external_memory: 318824
}

v8.queryObjects(ctor[, options])

추가된 버전: v22.0.0, v20.13.0

[Stable: 1 - 실험적]

Stable: 1 안정성: 1.1 - 활발한 개발 중

  • ctor <Function> 힙에서 대상 객체를 필터링하기 위해 프로토타입 체인에서 검색하는 데 사용할 수 있는 생성자입니다.

  • options <undefined> | <Object>

    • format <string> 'count'이면 일치하는 객체의 수가 반환됩니다. 'summary'이면 일치하는 객체의 요약 문자열이 있는 배열이 반환됩니다.
  • 반환값: {number|Array

이것은 Chromium DevTools 콘솔에서 제공하는 queryObjects() 콘솔 API와 유사합니다. 전체 가비지 컬렉션 후 힙에서 프로토타입 체인에 일치하는 생성자를 가진 객체를 검색하는 데 사용할 수 있으며, 이는 메모리 누수 회귀 테스트에 유용할 수 있습니다. 놀라운 결과를 피하기 위해 사용자는 자신이 제어하지 않는 구현을 가진 생성자 또는 응용 프로그램의 다른 당사자가 호출할 수 있는 생성자에서 이 API를 사용하지 않아야 합니다.

우발적인 누출을 방지하기 위해 이 API는 발견된 객체에 대한 원시 참조를 반환하지 않습니다. 기본적으로 발견된 객체의 수를 반환합니다. options.format'summary'이면 각 객체에 대한 간략한 문자열 표현이 포함된 배열을 반환합니다. 이 API에서 제공되는 가시성은 힙 스냅샷이 제공하는 것과 유사하지만 사용자는 직렬화 및 구문 분석 비용을 절약하고 검색 중에 대상 객체를 직접 필터링할 수 있습니다.

현재 실행 컨텍스트에서 생성된 객체만 결과에 포함됩니다.

js
const { queryObjects } = require('node:v8')
class A {
  foo = 'bar'
}
console.log(queryObjects(A)) // 0
const a = new A()
console.log(queryObjects(A)) // 1
// [ "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }))

class B extends A {
  bar = 'qux'
}
const b = new B()
console.log(queryObjects(B)) // 1
// [ "B { foo: 'bar', bar: 'qux' }" ]
console.log(queryObjects(B, { format: 'summary' }))

// 생성자에서 상속된 자식 클래스가 있는 경우,
// 생성자는 자식 클래스의 프로토타입 체인에도 표시되므로
// 자식 클래스의 프로토타입도 결과에 포함됩니다.
console.log(queryObjects(A)) // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }))
js
import { queryObjects } from 'node:v8'
class A {
  foo = 'bar'
}
console.log(queryObjects(A)) // 0
const a = new A()
console.log(queryObjects(A)) // 1
// [ "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }))

class B extends A {
  bar = 'qux'
}
const b = new B()
console.log(queryObjects(B)) // 1
// [ "B { foo: 'bar', bar: 'qux' }" ]
console.log(queryObjects(B, { format: 'summary' }))

// 생성자에서 상속된 자식 클래스가 있는 경우,
// 생성자는 자식 클래스의 프로토타입 체인에도 표시되므로
// 자식 클래스의 프로토타입도 결과에 포함됩니다.
console.log(queryObjects(A)) // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }))

v8.setFlagsFromString(flags)

추가된 버전: v1.0.0

v8.setFlagsFromString() 메서드는 V8 명령줄 플래그를 프로그래밍 방식으로 설정하는 데 사용할 수 있습니다. 이 메서드는 주의해서 사용해야 합니다. VM이 시작된 후 설정을 변경하면 충돌 및 데이터 손실을 포함한 예측할 수 없는 동작이 발생하거나 아무 동작도 하지 않을 수 있습니다.

Node.js 버전에 사용할 수 있는 V8 옵션은 node --v8-options를 실행하여 확인할 수 있습니다.

사용법:

js
// 1분 동안 GC 이벤트를 stdout에 출력합니다.
const v8 = require('node:v8')
v8.setFlagsFromString('--trace_gc')
setTimeout(() => {
  v8.setFlagsFromString('--notrace_gc')
}, 60e3)

v8.stopCoverage()

추가된 버전: v15.1.0, v14.18.0, v12.22.0

v8.stopCoverage() 메서드를 사용하면 NODE_V8_COVERAGE에서 시작된 커버리지 수집을 중지하여 V8이 실행 횟수 기록을 해제하고 코드를 최적화할 수 있습니다. 사용자가 필요에 따라 커버리지를 수집하려는 경우 v8.takeCoverage()와 함께 사용할 수 있습니다.

v8.takeCoverage()

추가된 버전: v15.1.0, v14.18.0, v12.22.0

v8.takeCoverage() 메서드를 사용하면 NODE_V8_COVERAGE에서 시작된 커버리지를 필요에 따라 디스크에 쓸 수 있습니다. 이 메서드는 프로세스 수명 동안 여러 번 호출할 수 있습니다. 호출할 때마다 실행 카운터가 재설정되고 새로운 커버리지 보고서가 NODE_V8_COVERAGE에 지정된 디렉토리에 기록됩니다.

프로세스가 종료되기 직전에도 v8.stopCoverage()가 프로세스 종료 전에 호출되지 않는 한 마지막 커버리지가 디스크에 기록됩니다.

v8.writeHeapSnapshot([filename[,options]])

[기록]

버전변경 사항
v19.1.0힙 스냅샷을 구성하는 옵션을 지원합니다.
v18.0.0파일을 쓸 수 없는 경우 예외가 발생합니다.
v18.0.0반환된 오류 코드를 모든 플랫폼에서 일관되게 만듭니다.
v11.13.0추가된 버전: v11.13.0
  • filename <string> V8 힙 스냅샷을 저장할 파일 경로입니다. 지정하지 않으면 'Heap-${yyyymmdd}-${hhmmss}-${pid}-${thread_id}.heapsnapshot' 패턴의 파일 이름이 생성됩니다. 여기서 {pid}는 Node.js 프로세스의 PID이고, {thread_id}writeHeapSnapshot()이 메인 Node.js 스레드에서 호출될 때 0이거나 워커 스레드의 ID입니다.

  • options <Object>

    • exposeInternals <boolean> true이면 힙 스냅샷에서 내부를 노출합니다. 기본값: false.
    • exposeNumericValues <boolean> true이면 인공 필드에서 숫자 값을 노출합니다. 기본값: false.
  • 반환 값: <string> 스냅샷이 저장된 파일 이름입니다.

현재 V8 힙의 스냅샷을 생성하고 JSON 파일에 씁니다. 이 파일은 Chrome DevTools와 같은 도구와 함께 사용하기 위한 것입니다. JSON 스키마는 문서화되지 않았으며 V8 엔진에 특정하며 V8 버전마다 변경될 수 있습니다.

힙 스냅샷은 단일 V8 격리에 특화되어 있습니다. 워커 스레드를 사용하는 경우 메인 스레드에서 생성된 힙 스냅샷에는 워커에 대한 정보가 포함되지 않으며, 반대의 경우도 마찬가지입니다.

힙 스냅샷을 만들려면 스냅샷 생성 당시의 힙 크기의 약 두 배의 메모리가 필요합니다. 이로 인해 OOM 킬러가 프로세스를 종료할 위험이 있습니다.

스냅샷 생성은 힙 크기에 따라 이벤트 루프를 차단하는 동기 작업입니다.

js
const { writeHeapSnapshot } = require('node:v8')
const { Worker, isMainThread, parentPort } = require('node:worker_threads')

if (isMainThread) {
  const worker = new Worker(__filename)

  worker.once('message', filename => {
    console.log(`워커 힙덤프: ${filename}`)
    // 이제 메인 스레드의 힙덤프를 가져옵니다.
    console.log(`메인 스레드 힙덤프: ${writeHeapSnapshot()}`)
  })

  // 워커에게 힙덤프를 생성하도록 지시합니다.
  worker.postMessage('heapdump')
} else {
  parentPort.once('message', message => {
    if (message === 'heapdump') {
      // 워커에 대한 힙덤프를 생성합니다.
      // 부모에게 파일 이름을 반환합니다.
      parentPort.postMessage(writeHeapSnapshot())
    }
  })
}

v8.setHeapSnapshotNearHeapLimit(limit)

추가된 버전: v18.10.0, v16.18.0

[안정성: 1 - 실험적]

안정성: 1 안정성: 1 - 실험적

--heapsnapshot-near-heap-limit가 이미 명령줄에서 설정되었거나 API가 두 번 이상 호출된 경우에는 API가 아무 작업도 수행하지 않습니다. limit는 양의 정수여야 합니다. 자세한 내용은 --heapsnapshot-near-heap-limit를 참조하십시오.

직렬화 API

직렬화 API는 HTML 구조적 복제 알고리즘과 호환되는 방식으로 JavaScript 값을 직렬화하는 방법을 제공합니다.

형식은 하위 호환 가능합니다(즉, 디스크에 저장해도 안전). 동일한 JavaScript 값이라도 다른 직렬화 출력이 생성될 수 있습니다.

v8.serialize(value)

추가된 버전: v8.0.0

DefaultSerializer를 사용하여 value를 버퍼로 직렬화합니다.

ERR_BUFFER_TOO_LARGEbuffer.constants.MAX_LENGTH보다 큰 버퍼를 필요로 하는 거대한 객체를 직렬화하려고 할 때 발생합니다.

v8.deserialize(buffer)

추가된 버전: v8.0.0

기본 옵션과 함께 DefaultDeserializer를 사용하여 버퍼에서 JS 값을 읽습니다.

클래스: v8.Serializer

추가된 버전: v8.0.0

new Serializer()

새로운 Serializer 객체를 생성합니다.

serializer.writeHeader()

직렬화 형식 버전을 포함하는 헤더를 씁니다.

serializer.writeValue(value)

JavaScript 값을 직렬화하고 직렬화된 표현을 내부 버퍼에 추가합니다.

value를 직렬화할 수 없으면 오류가 발생합니다.

serializer.releaseBuffer()

저장된 내부 버퍼를 반환합니다. 버퍼가 해제되면 이 직렬 변환기를 사용해서는 안 됩니다. 이전 쓰기가 실패한 경우 이 메서드를 호출하면 정의되지 않은 동작이 발생합니다.

serializer.transferArrayBuffer(id, arrayBuffer)

  • id <integer> 32비트 부호 없는 정수입니다.
  • arrayBuffer <ArrayBuffer> ArrayBuffer 인스턴스입니다.

ArrayBuffer를 해당 내용이 대역 외로 전송된 것으로 표시합니다. 역직렬화 컨텍스트에서 해당하는 ArrayBufferdeserializer.transferArrayBuffer()에 전달합니다.

serializer.writeUint32(value)

원시 32비트 부호 없는 정수를 씁니다. 사용자 정의 serializer._writeHostObject() 내에서 사용합니다.

serializer.writeUint64(hi, lo)

원시 64비트 부호 없는 정수를 상위 및 하위 32비트 부분으로 나누어 씁니다. 사용자 정의 serializer._writeHostObject() 내에서 사용합니다.

serializer.writeDouble(value)

JS number 값을 씁니다. 사용자 정의 serializer._writeHostObject() 내부에서 사용합니다.

serializer.writeRawBytes(buffer)

직렬화기의 내부 버퍼에 원시 바이트를 씁니다. 역직렬화기는 버퍼의 길이를 계산하는 방법이 필요합니다. 사용자 정의 serializer._writeHostObject() 내부에서 사용합니다.

serializer._writeHostObject(object)

이 메서드는 일부 종류의 호스트 객체, 즉 네이티브 C++ 바인딩으로 생성된 객체를 쓰는 데 사용됩니다. object를 직렬화할 수 없는 경우 적절한 예외를 발생시켜야 합니다.

이 메서드는 Serializer 클래스 자체에는 존재하지 않지만 서브클래스에서 제공할 수 있습니다.

serializer._getDataCloneError(message)

이 메서드는 객체를 복제할 수 없을 때 발생할 오류 객체를 생성하는 데 사용됩니다.

이 메서드는 기본적으로 Error 생성자를 사용하며 서브클래스에서 재정의할 수 있습니다.

serializer._getSharedArrayBufferId(sharedArrayBuffer)

이 메서드는 직렬화기가 SharedArrayBuffer 객체를 직렬화하려고 할 때 호출됩니다. 이 SharedArrayBuffer가 이미 직렬화된 경우 동일한 ID를 사용하여 객체에 대한 부호 없는 32비트 정수 ID를 반환해야 합니다. 역직렬화할 때 이 ID는 deserializer.transferArrayBuffer()로 전달됩니다.

객체를 직렬화할 수 없는 경우 예외를 발생시켜야 합니다.

이 메서드는 Serializer 클래스 자체에는 존재하지 않지만 서브클래스에서 제공할 수 있습니다.

serializer._setTreatArrayBufferViewsAsHostObjects(flag)

TypedArrayDataView 객체를 호스트 객체로 처리할지 여부를 나타냅니다. 즉, serializer._writeHostObject()로 전달합니다.

클래스: v8.Deserializer

v8.0.0에 추가됨

new Deserializer(buffer)

새로운 Deserializer 객체를 만듭니다.

deserializer.readHeader()

헤더(포맷 버전 포함)를 읽고 유효성을 검사합니다. 예를 들어, 유효하지 않거나 지원되지 않는 와이어 포맷을 거부할 수 있습니다. 이 경우 Error가 발생합니다.

deserializer.readValue()

버퍼에서 JavaScript 값을 역직렬화하고 반환합니다.

deserializer.transferArrayBuffer(id, arrayBuffer)

ArrayBuffer의 내용이 대역 외로 전송된 것으로 표시합니다. 직렬화 컨텍스트에서 해당하는 ArrayBufferserializer.transferArrayBuffer()로 전달합니다(또는 SharedArrayBuffer의 경우 serializer._getSharedArrayBufferId()에서 id를 반환합니다).

deserializer.getWireFormatVersion()

기본 와이어 형식 버전을 읽습니다. 주로 이전 와이어 형식 버전을 읽는 레거시 코드에 유용할 수 있습니다. .readHeader() 전에 호출하면 안 됩니다.

deserializer.readUint32()

원시 32비트 부호 없는 정수를 읽고 반환합니다. 사용자 지정 deserializer._readHostObject() 내부에서 사용됩니다.

deserializer.readUint64()

원시 64비트 부호 없는 정수를 읽고 두 개의 32비트 부호 없는 정수 항목이 있는 배열 [hi, lo]로 반환합니다. 사용자 지정 deserializer._readHostObject() 내부에서 사용됩니다.

deserializer.readDouble()

JS number 값을 읽습니다. 사용자 지정 deserializer._readHostObject() 내부에서 사용됩니다.

deserializer.readRawBytes(length)

deserializer의 내부 버퍼에서 원시 바이트를 읽습니다. length 매개변수는 serializer.writeRawBytes()에 전달된 버퍼의 길이와 일치해야 합니다. 사용자 지정 deserializer._readHostObject() 내부에서 사용됩니다.

deserializer._readHostObject()

이 메서드는 일종의 호스트 객체, 즉 네이티브 C++ 바인딩으로 생성된 객체를 읽기 위해 호출됩니다. 데이터를 역직렬화할 수 없는 경우 적절한 예외가 발생해야 합니다.

이 메서드는 Deserializer 클래스 자체에는 없지만 하위 클래스에서 제공할 수 있습니다.

클래스: v8.DefaultSerializer

추가된 버전: v8.0.0

Serializer의 하위 클래스로, TypedArray (특히 Buffer) 및 DataView 객체를 호스트 객체로 직렬화하고, 참조하는 기본 ArrayBuffer의 일부만 저장합니다.

클래스: v8.DefaultDeserializer

추가된 버전: v8.0.0

DefaultSerializer가 작성한 형식에 해당하는 Deserializer의 하위 클래스입니다.

Promise 훅

promiseHooks 인터페이스는 promise 수명 주기 이벤트를 추적하는 데 사용할 수 있습니다. 모든 비동기 활동을 추적하려면 다른 비동기 리소스에 대한 이벤트 외에도 promise 수명 주기 이벤트를 생성하기 위해 내부적으로 이 모듈을 사용하는 async_hooks를 참조하십시오. 요청 컨텍스트 관리에 대해서는 AsyncLocalStorage를 참조하십시오.

js
import { promiseHooks } from 'node:v8'

// 프로미스에서 생성되는 수명 주기 이벤트는 네 가지입니다.

// `init` 이벤트는 프로미스 생성을 나타냅니다. 이는 `new Promise(...)`를 사용한 직접적인 생성일 수도 있고,
// `then()` 또는 `catch()`와 같은 연속일 수도 있습니다. 또한 비동기 함수가 호출되거나 `await`를 수행할 때마다 발생합니다.
// 연속 프로미스가 생성되면 `parent`는 연속 대상이 되는 프로미스가 됩니다.
function init(promise, parent) {
  console.log('프로미스가 생성되었습니다.', { promise, parent })
}

// `settled` 이벤트는 프로미스가 해결 또는 거부 값을 받을 때 발생합니다.
// 이는 프로미스가 아닌 입력에 `Promise.resolve()`를 사용할 때처럼 동기적으로 발생할 수 있습니다.
function settled(promise) {
  console.log('프로미스가 해결되거나 거부되었습니다.', { promise })
}

// `before` 이벤트는 `then()` 또는 `catch()` 핸들러가 실행되기 직전 또는
// `await`가 실행을 재개하기 직전에 실행됩니다.
function before(promise) {
  console.log('프로미스가 then 핸들러를 호출하려고 합니다.', { promise })
}

// `after` 이벤트는 `then()` 핸들러가 실행된 직후 또는
// 다른 핸들러에서 재개한 후 `await`가 시작될 때 실행됩니다.
function after(promise) {
  console.log('프로미스가 then 핸들러 호출을 완료했습니다.', { promise })
}

// 수명 주기 훅은 개별적으로 시작 및 중지할 수 있습니다.
const stopWatchingInits = promiseHooks.onInit(init)
const stopWatchingSettleds = promiseHooks.onSettled(settled)
const stopWatchingBefores = promiseHooks.onBefore(before)
const stopWatchingAfters = promiseHooks.onAfter(after)

// 또는 그룹으로 시작 및 중지할 수 있습니다.
const stopHookSet = promiseHooks.createHook({
  init,
  settled,
  before,
  after,
})

// 훅을 중지하려면 생성 시 반환된 함수를 호출합니다.
stopWatchingInits()
stopWatchingSettleds()
stopWatchingBefores()
stopWatchingAfters()
stopHookSet()

promiseHooks.onInit(init)

추가된 버전: v17.1.0, v16.14.0

  • init <함수> 프로미스가 생성될 때 호출할 init 콜백입니다.
  • 반환 값: <함수> 후크를 중지하는 호출입니다.

init 후크는 일반 함수여야 합니다. 비동기 함수를 제공하면 무한 마이크로태스크 루프를 생성하므로 오류가 발생합니다.

js
import { promiseHooks } from 'node:v8'

const stop = promiseHooks.onInit((promise, parent) => {})
js
const { promiseHooks } = require('node:v8')

const stop = promiseHooks.onInit((promise, parent) => {})

promiseHooks.onSettled(settled)

추가된 버전: v17.1.0, v16.14.0

  • settled <함수> 프로미스가 이행되거나 거부될 때 호출할 settled 콜백입니다.
  • 반환 값: <함수> 후크를 중지하는 호출입니다.

settled 후크는 일반 함수여야 합니다. 비동기 함수를 제공하면 무한 마이크로태스크 루프를 생성하므로 오류가 발생합니다.

js
import { promiseHooks } from 'node:v8'

const stop = promiseHooks.onSettled(promise => {})
js
const { promiseHooks } = require('node:v8')

const stop = promiseHooks.onSettled(promise => {})

promiseHooks.onBefore(before)

추가된 버전: v17.1.0, v16.14.0

  • before <함수> 프로미스 연속이 실행되기 전에 호출할 before 콜백입니다.
  • 반환 값: <함수> 후크를 중지하는 호출입니다.

before 후크는 일반 함수여야 합니다. 비동기 함수를 제공하면 무한 마이크로태스크 루프를 생성하므로 오류가 발생합니다.

js
import { promiseHooks } from 'node:v8'

const stop = promiseHooks.onBefore(promise => {})
js
const { promiseHooks } = require('node:v8')

const stop = promiseHooks.onBefore(promise => {})

promiseHooks.onAfter(after)

추가된 버전: v17.1.0, v16.14.0

after 후크는 일반 함수여야 합니다. 비동기 함수를 제공하면 무한 마이크로태스크 루프가 생성되므로 오류가 발생합니다.

js
import { promiseHooks } from 'node:v8'

const stop = promiseHooks.onAfter(promise => {})
js
const { promiseHooks } = require('node:v8')

const stop = promiseHooks.onAfter(promise => {})

promiseHooks.createHook(callbacks)

추가된 버전: v17.1.0, v16.14.0

후크 콜백은 일반 함수여야 합니다. 비동기 함수를 제공하면 무한 마이크로태스크 루프가 생성되므로 오류가 발생합니다.

각 약속의 수명 주기 이벤트에 대해 호출할 함수를 등록합니다.

콜백 init()/before()/after()/settled()는 약속 수명 동안 각각의 이벤트에 대해 호출됩니다.

모든 콜백은 선택 사항입니다. 예를 들어, 약속 생성만 추적해야 하는 경우 init 콜백만 전달하면 됩니다. callbacks에 전달할 수 있는 모든 함수의 세부 사항은 후크 콜백 섹션에 있습니다.

js
import { promiseHooks } from 'node:v8'

const stopAll = promiseHooks.createHook({
  init(promise, parent) {},
})
js
const { promiseHooks } = require('node:v8')

const stopAll = promiseHooks.createHook({
  init(promise, parent) {},
})

Hook 콜백

Promise의 수명 주기에서 중요한 이벤트는 Promise 생성, 연속 처리기 호출 전/후 또는 await 주변, Promise가 이행 또는 거부될 때의 네 가지 영역으로 분류됩니다.

이러한 후크는 async_hooks와 유사하지만 destroy 후크가 없습니다. 다른 유형의 비동기 리소스는 일반적으로 소켓 또는 파일 설명자를 나타내며, "닫힘" 상태가 명확하여 Promise는 코드가 여전히 접근할 수 있는 한 계속 사용할 수 있지만 destroy 수명 주기 이벤트를 나타냅니다. 가비지 컬렉션 추적은 Promise를 async_hooks 이벤트 모델에 맞추기 위해 사용되지만 이 추적은 매우 비용이 많이 들고 가비지 컬렉션이 되지 않을 수도 있습니다.

Promise는 Promise 후크 메커니즘을 통해 수명 주기가 추적되는 비동기 리소스이므로 init(), before(), after(), settled() 콜백은 더 많은 Promise를 생성하여 무한 루프를 생성하므로 비동기 함수가 되어서는 안 됩니다.

이 API는 Promise 이벤트를 async_hooks에 공급하는 데 사용되지만 둘 사이의 순서는 정의되지 않습니다. 두 API 모두 멀티 테넌트이므로 서로에 대해 임의의 순서로 이벤트를 생성할 수 있습니다.

init(promise, parent)

  • promise <Promise> 생성 중인 Promise입니다.
  • parent <Promise> 해당하는 경우 계속된 Promise입니다.

Promise가 생성될 때 호출됩니다. 이는 해당 before/after 이벤트가 발생한다는 의미가 아니라 가능성만 존재한다는 의미입니다. 이는 연속이 전혀 이루어지지 않고 Promise가 생성된 경우에 발생합니다.

before(promise)

Promise 연속이 실행되기 전에 호출됩니다. 이는 then(), catch(), finally() 처리기 또는 await 재개 형태일 수 있습니다.

before 콜백은 0에서 N번 호출됩니다. 일반적으로 Promise에 대한 연속이 전혀 이루어지지 않은 경우 before 콜백은 0번 호출됩니다. 동일한 Promise에서 많은 연속이 이루어진 경우 before 콜백은 여러 번 호출될 수 있습니다.

after(promise)

promise 연속 실행 직후에 즉시 호출됩니다. 이는 then(), catch(), 또는 finally() 핸들러 이후 또는 다른 await 이후의 await 전에 발생할 수 있습니다.

settled(promise)

promise가 해결 또는 거부 값을 받을 때 호출됩니다. 이는 Promise.resolve() 또는 Promise.reject()의 경우 동기적으로 발생할 수 있습니다.

Startup Snapshot API

추가됨: v18.6.0, v16.17.0

[안정성: 1 - 실험적]

안정성: 1 안정성: 1 - 실험적

v8.startupSnapshot 인터페이스는 사용자 정의 시작 스냅샷을 위한 직렬화 및 역직렬화 후크를 추가하는 데 사용할 수 있습니다.

bash
$ node --snapshot-blob snapshot.blob --build-snapshot entry.js
# 스냅샷으로 프로세스를 시작합니다. {#this-launches-a-process-with-the-snapshot}
$ node --snapshot-blob snapshot.blob

위의 예에서 entry.jsv8.startupSnapshot 인터페이스의 메서드를 사용하여 직렬화 중에 스냅샷에 사용자 정의 객체에 대한 정보를 저장하는 방법과 스냅샷의 역직렬화 중에 해당 객체를 동기화하는 데 정보를 사용하는 방법을 지정할 수 있습니다. 예를 들어, entry.js에 다음 스크립트가 포함된 경우:

js
'use strict'

const fs = require('node:fs')
const zlib = require('node:zlib')
const path = require('node:path')
const assert = require('node:assert')

const v8 = require('node:v8')

class BookShelf {
  storage = new Map()

  // 디렉토리에서 일련의 파일을 읽고 스토리지에 저장합니다.
  constructor(directory, books) {
    for (const book of books) {
      this.storage.set(book, fs.readFileSync(path.join(directory, book)))
    }
  }

  static compressAll(shelf) {
    for (const [book, content] of shelf.storage) {
      shelf.storage.set(book, zlib.gzipSync(content))
    }
  }

  static decompressAll(shelf) {
    for (const [book, content] of shelf.storage) {
      shelf.storage.set(book, zlib.gunzipSync(content))
    }
  }
}

// 여기의 __dirname은 스냅샷 빌드 시간 동안 스냅샷 스크립트가 배치되는 위치입니다.
const shelf = new BookShelf(__dirname, ['book1.en_US.txt', 'book1.es_ES.txt', 'book2.zh_CN.txt'])

assert(v8.startupSnapshot.isBuildingSnapshot())
// 스냅샷 직렬화 시 크기를 줄이기 위해 책을 압축합니다.
v8.startupSnapshot.addSerializeCallback(BookShelf.compressAll, shelf)
// 스냅샷 역직렬화 시 책을 압축 해제합니다.
v8.startupSnapshot.addDeserializeCallback(BookShelf.decompressAll, shelf)
v8.startupSnapshot.setDeserializeMainFunction(shelf => {
  // process.env 및 process.argv는 스냅샷 역직렬화 중에 새로 고쳐집니다.
  const lang = process.env.BOOK_LANG || 'en_US'
  const book = process.argv[1]
  const name = `${book}.${lang}.txt`
  console.log(shelf.storage.get(name))
}, shelf)

결과 바이너리는 시작 중에 스냅샷에서 역직렬화된 데이터를 출력하고 시작된 프로세스의 새로 고쳐진 process.envprocess.argv를 사용합니다.

bash
$ BOOK_LANG=es_ES node --snapshot-blob snapshot.blob book1
# 스냅샷에서 역직렬화된 book1.es_ES.txt의 내용을 출력합니다. {#prints-content-of-book1es_estxt-deserialized-from-the-snapshot}

현재 사용자 영역 스냅샷에서 역직렬화된 애플리케이션은 다시 스냅샷을 찍을 수 없으므로 이러한 API는 사용자 영역 스냅샷에서 역직렬화되지 않은 애플리케이션에서만 사용할 수 있습니다.

v8.startupSnapshot.addSerializeCallback(callback[, data])

추가된 버전: v18.6.0, v16.17.0

  • callback <함수> 직렬화 전에 호출할 콜백입니다.
  • data <any> 선택적 데이터로, 호출될 때 callback에 전달됩니다.

Node.js 인스턴스가 스냅샷으로 직렬화되어 종료되기 직전에 호출되는 콜백을 추가합니다. 이는 직렬화해서는 안 되거나 직렬화할 수 없는 리소스를 해제하거나 사용자 데이터를 직렬화에 더 적합한 형태로 변환하는 데 사용할 수 있습니다.

콜백은 추가된 순서대로 실행됩니다.

v8.startupSnapshot.addDeserializeCallback(callback[, data])

추가된 버전: v18.6.0, v16.17.0

  • callback <함수> 스냅샷이 역직렬화된 후에 호출할 콜백입니다.
  • data <any> 선택적 데이터로, 호출될 때 callback에 전달됩니다.

Node.js 인스턴스가 스냅샷에서 역직렬화될 때 호출되는 콜백을 추가합니다. callbackdata(제공된 경우)는 스냅샷으로 직렬화되며, 애플리케이션의 상태를 다시 초기화하거나 애플리케이션이 스냅샷에서 다시 시작될 때 필요한 리소스를 다시 획득하는 데 사용할 수 있습니다.

콜백은 추가된 순서대로 실행됩니다.

v8.startupSnapshot.setDeserializeMainFunction(callback[, data])

추가된 버전: v18.6.0, v16.17.0

  • callback <함수> 스냅샷이 역직렬화된 후 진입점으로 호출할 콜백입니다.
  • data <any> 선택적 데이터로, 호출될 때 callback에 전달됩니다.

스냅샷에서 역직렬화될 때 Node.js 애플리케이션의 진입점을 설정합니다. 이는 스냅샷 빌드 스크립트에서 한 번만 호출할 수 있습니다. 호출되면 역직렬화된 애플리케이션은 시작하기 위해 추가 진입점 스크립트가 더 이상 필요하지 않으며 역직렬화된 데이터(제공된 경우)와 함께 콜백을 호출합니다. 그렇지 않으면 역직렬화된 애플리케이션에 진입점 스크립트를 제공해야 합니다.

v8.startupSnapshot.isBuildingSnapshot()

추가된 버전: v18.6.0, v16.17.0

Node.js 인스턴스가 스냅샷을 빌드하기 위해 실행 중인 경우 true를 반환합니다.

클래스: v8.GCProfiler

추가된 버전: v19.6.0, v18.15.0

이 API는 현재 스레드에서 GC 데이터를 수집합니다.

new v8.GCProfiler()

추가된 버전: v19.6.0, v18.15.0

v8.GCProfiler 클래스의 새 인스턴스를 만듭니다.

profiler.start()

추가된 버전: v19.6.0, v18.15.0

GC 데이터 수집을 시작합니다.

profiler.stop()

추가된 버전: v19.6.0, v18.15.0

GC 데이터 수집을 중지하고 객체를 반환합니다. 객체의 내용은 다음과 같습니다.

json
{
  "version": 1,
  "startTime": 1674059033862,
  "statistics": [
    {
      "gcType": "Scavenge",
      "beforeGC": {
        "heapStatistics": {
          "totalHeapSize": 5005312,
          "totalHeapSizeExecutable": 524288,
          "totalPhysicalSize": 5226496,
          "totalAvailableSize": 4341325216,
          "totalGlobalHandlesSize": 8192,
          "usedGlobalHandlesSize": 2112,
          "usedHeapSize": 4883840,
          "heapSizeLimit": 4345298944,
          "mallocedMemory": 254128,
          "externalMemory": 225138,
          "peakMallocedMemory": 181760
        },
        "heapSpaceStatistics": [
          {
            "spaceName": "read_only_space",
            "spaceSize": 0,
            "spaceUsedSize": 0,
            "spaceAvailableSize": 0,
            "physicalSpaceSize": 0
          }
        ]
      },
      "cost": 1574.14,
      "afterGC": {
        "heapStatistics": {
          "totalHeapSize": 6053888,
          "totalHeapSizeExecutable": 524288,
          "totalPhysicalSize": 5500928,
          "totalAvailableSize": 4341101384,
          "totalGlobalHandlesSize": 8192,
          "usedGlobalHandlesSize": 2112,
          "usedHeapSize": 4059096,
          "heapSizeLimit": 4345298944,
          "mallocedMemory": 254128,
          "externalMemory": 225138,
          "peakMallocedMemory": 181760
        },
        "heapSpaceStatistics": [
          {
            "spaceName": "read_only_space",
            "spaceSize": 0,
            "spaceUsedSize": 0,
            "spaceAvailableSize": 0,
            "physicalSpaceSize": 0
          }
        ]
      }
    }
  ],
  "endTime": 1674059036865
}

다음은 예시입니다.

js
const { GCProfiler } = require('node:v8')
const profiler = new GCProfiler()
profiler.start()
setTimeout(() => {
  console.log(profiler.stop())
}, 1000)