V8#

node:v8 模块提供了特定于构建在 Node.js 二进制文件中的 V8 版本的 API。可以通过以下方式访问:

import v8 from 'node:v8';
const v8 = require('node:v8');

v8.cachedDataVersionTag()#

返回一个整数,该整数表示从 V8 版本、命令行标志和检测到的 CPU 特性导出的版本标签。这对于确定 vm.ScriptcachedData 缓冲区是否与此 V8 实例兼容非常有用。

console.log(v8.cachedDataVersionTag()); // 3947234607
// The value returned by v8.cachedDataVersionTag() is derived from the V8
// version, command-line flags, and detected CPU features. Test that the value
// does indeed update when flags are toggled.
v8.setFlagsFromString('--allow_natives_syntax');
console.log(v8.cachedDataVersionTag()); // 183726201

v8.getHeapCodeStatistics()#

获取有关堆中代码及其元数据的统计信息,请参阅 V8 GetHeapCodeAndMetadataStatistics API。返回具有以下属性的对象:

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

v8.getHeapSnapshot([options])#

  • options <Object>

    • exposeInternals <boolean> 如果为 true,则在堆快照中公开内部信息。默认值: false
    • exposeNumericValues <boolean> 如果为 true,则在人工字段中公开数值。默认值: false
  • 返回:<stream.Readable> 包含 V8 堆快照的可读流。

生成当前 V8 堆的快照,并返回一个可读流,该流可用于读取 JSON 序列化的表示。这种 JSON 流格式旨在与 Chrome DevTools 等工具配合使用。JSON 模式未记录且特定于 V8 引擎。因此,该模式可能会随 V8 版本的更迭而更改。

创建堆快照需要大约相当于创建快照时堆大小两倍的内存。这会导致 OOM killer 终止进程的风险。

生成快照是一个同步操作,它会阻塞事件循环,持续时间取决于堆大小。

// Print heap snapshot to the console
import { getHeapSnapshot } from 'node:v8';
import process from 'node:process';
const stream = getHeapSnapshot();
stream.pipe(process.stdout);
// Print heap snapshot to the console
const v8 = require('node:v8');
const process = require('node:process');
const stream = v8.getHeapSnapshot();
stream.pipe(process.stdout);

v8.getHeapSpaceStatistics()#

返回有关 V8 堆空间的统计信息,即构成 V8 堆的段。由于统计信息是通过 V8 GetHeapSpaceStatistics 函数提供的,因此无法保证堆空间的顺序或堆空间的可用性,并且可能随 V8 版本的更迭而更改。

返回的值是一个包含以下属性的对象数组:

[
  {
    "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()#

返回一个具有以下属性的对象:

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 是一个 0/1 布尔值,表示是否启用了 --zap_code_space 选项。这使得 V8 用位模式覆盖堆垃圾。RSS 占用量(常驻集大小)会变大,因为它会持续触及所有堆页面,这使得操作系统不太可能将它们换出。

number_of_native_contexts native_context 的值是当前活动的顶级上下文数量。此数字随时间增加表示存在内存泄漏。

number_of_detached_contexts detached_context 的值是已分离但尚未被垃圾回收的上下文数量。此数字非零表示存在潜在的内存泄漏。

total_global_handles_size total_global_handles_size 的值是 V8 全局句柄的总内存大小。

used_global_handles_size used_global_handles_size 的值是 V8 全局句柄已使用的内存大小。

external_memory external_memory 的值是数组缓冲区和外部字符串的内存大小。

total_allocated_bytes 自 Isolate 创建以来总共分配的字节数。

{
  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.getCppHeapStatistics([detailLevel])#

使用 V8 CollectStatistics() 函数检索有关内存消耗和利用率的 CppHeap 统计信息,该函数可能会随 V8 版本而更改。

  • detailLevel <string> | <undefined>: 默认值: 'detailed'。指定返回的统计信息的详细程度。可接受的值为:
    • 'brief':简要统计信息仅包含整个堆的顶级已分配和已使用内存统计信息。
    • 'detailed':详细统计信息还包含每个空间和页面的细分,以及空闲列表统计信息和对象类型直方图。

它返回一个结构类似于 cppgc::HeapStatistics 对象。有关该对象属性的更多信息,请参阅 V8 文档

// Detailed
({
  committed_size_bytes: 131072,
  resident_size_bytes: 131072,
  used_size_bytes: 152,
  space_statistics: [
    {
      name: 'NormalPageSpace0',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace1',
      committed_size_bytes: 131072,
      resident_size_bytes: 131072,
      used_size_bytes: 152,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace2',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace3',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'LargePageSpace',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
  ],
  type_names: [],
  detail_level: 'detailed',
});
// Brief
({
  committed_size_bytes: 131072,
  resident_size_bytes: 131072,
  used_size_bytes: 128864,
  space_statistics: [],
  type_names: [],
  detail_level: 'brief',
});

v8.queryObjects(ctor[, options])#

  • ctor <Function> 可用于搜索原型链以过滤堆中目标对象的构造函数。
  • options <undefined> | <Object>
    • format <string> 如果为 'count',则返回匹配对象的计数。如果为 'summary',则返回一个包含匹配对象摘要字符串的数组。
  • 返回:{number|Array}}

这类似于 Chromium DevTools 控制台提供的 queryObjects() 控制台 API。它可用于在执行完整垃圾回收后搜索原型链上具有匹配构造函数的堆中对象,这对于内存泄漏回归测试非常有用。为了避免意外结果,用户应避免在无法控制实现的构造函数上使用此 API,或避免在应用程序中可由其他方调用的构造函数上使用此 API。

为了避免意外泄漏,此 API 不返回所找到对象的原始引用。默认情况下,它返回找到的对象的数量。如果 options.format'summary',则返回一个包含每个对象简短字符串表示形式的数组。此 API 提供的可见性类似于堆快照提供的可见性,同时用户可以节省序列化和解析的开销,并在搜索期间直接过滤目标对象。

结果中仅包含在当前执行上下文中创建的对象。

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' }));

// Note that, when there are child classes inheriting from a constructor,
// the constructor also shows up in the prototype chain of the child
// classes's prototype, so the child classes's prototype would also be
// included in the result.
console.log(queryObjects(A));  // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));
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' }));

// Note that, when there are child classes inheriting from a constructor,
// the constructor also shows up in the prototype chain of the child
// classes's prototype, so the child classes's prototype would also be
// included in the result.
console.log(queryObjects(A));  // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));

v8.setFlagsFromString(flags)#

v8.setFlagsFromString() 方法可用于以编程方式设置 V8 命令行标志。此方法应谨慎使用。在虚拟机启动后更改设置可能会导致不可预知的行为,包括崩溃和数据丢失;或者可能根本不起作用。

可通过运行 node --v8-options 来确定 Node.js 版本可用的 V8 选项。

用法

import { setFlagsFromString } from 'node:v8';
import { setInterval } from 'node:timers';

// setFlagsFromString to trace garbage collection events
setFlagsFromString('--trace-gc');

// Trigger GC events by using some memory
let arrays = [];
const interval = setInterval(() => {
  for (let i = 0; i < 500; i++) {
    arrays.push(new Array(10000).fill(Math.random()));
  }

  if (arrays.length > 5000) {
    arrays = arrays.slice(-1000);
  }

  console.log(`\n* Created ${arrays.length} arrays\n`);
}, 100);

// setFlagsFromString to stop tracing GC events after 1.5 seconds
setTimeout(() => {
  setFlagsFromString('--notrace-gc');
  console.log('\nStopped tracing!\n');
}, 1500);

// Stop triggering GC events altogether after 2.5 seconds
setTimeout(() => {
  clearInterval(interval);
}, 2500);
const { setFlagsFromString } = require('node:v8');
const { setInterval } = require('node:timers');

// setFlagsFromString to trace garbage collection events
setFlagsFromString('--trace-gc');

// Trigger GC events by using some memory
let arrays = [];
const interval = setInterval(() => {
  for (let i = 0; i < 500; i++) {
    arrays.push(new Array(10000).fill(Math.random()));
  }

  if (arrays.length > 5000) {
    arrays = arrays.slice(-1000);
  }

  console.log(`\n* Created ${arrays.length} arrays\n`);
}, 100);

// setFlagsFromString to stop tracing GC events after 1.5 seconds
setTimeout(() => {
  console.log('\nStopped tracing!\n');
  setFlagsFromString('--notrace-gc');
}, 1500);

// Stop triggering GC events altogether after 2.5 seconds
setTimeout(() => {
  clearInterval(interval);
}, 2500);

v8.stopCoverage()#

v8.stopCoverage() 方法允许用户停止由 NODE_V8_COVERAGE 启动的覆盖率收集,以便 V8 可以释放执行计数记录并优化代码。如果用户想要按需收集覆盖率,可以将其与 v8.takeCoverage() 结合使用。

v8.takeCoverage()#

v8.takeCoverage() 方法允许用户按需将由 NODE_V8_COVERAGE 启动的覆盖率写入磁盘。此方法可在进程生命周期内多次调用。每次调用都会重置执行计数器,并将新的覆盖率报告写入 NODE_V8_COVERAGE 指定的目录。

当进程即将退出时,除非在进程退出前调用了 v8.stopCoverage(),否则最后一次覆盖率仍会写入磁盘。

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

  • filename <string> 要保存 V8 堆快照的文件路径。如果未指定,将生成模式为 'Heap-${yyyymmdd}-${hhmmss}-${pid}-${thread_id}.heapsnapshot' 的文件名,其中 {pid} 是 Node.js 进程的 PID,当从主 Node.js 线程调用 writeHeapSnapshot(){thread_id}0,或者是工作线程的 ID。
  • options <Object>
  • exposeInternals <boolean> 如果为 true,则在堆快照中公开内部信息。默认值: false
  • exposeNumericValues <boolean> 如果为 true,则在人工字段中公开数值。默认值: false
  • 返回:<string> 保存快照的文件名。
  • 生成当前 V8 堆的快照并将其写入 JSON 文件。此文件旨在与 Chrome DevTools 等工具配合使用。JSON 模式未记录且特定于 V8 引擎,并且可能随 V8 版本的更迭而更改。

    堆快照特定于单个 V8 Isolate。使用 工作线程 时,从主线程生成的堆快照不包含有关工作线程的任何信息,反之亦然。

    创建堆快照需要大约相当于创建快照时堆大小两倍的内存。这会导致 OOM killer 终止进程的风险。

    生成快照是一个同步操作,它会阻塞事件循环,持续时间取决于堆大小。

    import { writeHeapSnapshot } from 'node:v8';
    import { Worker, isMainThread, parentPort } from 'node:worker_threads';
    import { fileURLToPath } from 'node:url';
    
    if (isMainThread) {
      const __filename = fileURLToPath(import.meta.url);
      const worker = new Worker(__filename);
    
      worker.once('message', (filename) => {
        console.log(`worker heapdump: ${filename}`);
        // Now get a heapdump for the main thread.
        console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
      });
    
      // Tell the worker to create a heapdump.
      worker.postMessage('heapdump');
    } else {
      parentPort.once('message', (message) => {
        if (message === 'heapdump') {
          // Generate a heapdump for the worker
          // and return the filename to the parent.
          parentPort.postMessage(writeHeapSnapshot());
        }
      });
    }
    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(`worker heapdump: ${filename}`);
        // Now get a heapdump for the main thread.
        console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
      });
    
      // Tell the worker to create a heapdump.
      worker.postMessage('heapdump');
    } else {
      parentPort.once('message', (message) => {
        if (message === 'heapdump') {
          // Generate a heapdump for the worker
          // and return the filename to the parent.
          parentPort.postMessage(writeHeapSnapshot());
        }
      });
    }
    

    v8.setHeapSnapshotNearHeapLimit(limit)#

    如果已从命令行设置了 --heapsnapshot-near-heap-limit 或多次调用该 API,则该 API 为无操作。limit 必须是正整数。有关详细信息,请参阅 --heapsnapshot-near-heap-limit

    序列化 API#

    序列化 API 提供了以与 HTML 结构化克隆算法 兼容的方式序列化 JavaScript 值的方法。

    该格式是向后兼容的(即可以安全地存储到磁盘)。相等的 JavaScript 值可能会产生不同的序列化输出。

    v8.serialize(value)#

    使用 DefaultSerializervalue 序列化为缓冲区。

    尝试序列化需要大于 buffer.constants.MAX_LENGTH 的缓冲区的大对象时,将抛出 ERR_BUFFER_TOO_LARGE

    v8.deserialize(buffer)#

    使用带有默认选项的 DefaultDeserializer 从缓冲区读取 JS 值。

    类:v8.Serializer#

    new Serializer()#

    创建一个新的 Serializer 对象。

    serializer.writeHeader()#

    写入头部,其中包含序列化格式版本。

    serializer.writeValue(value)#

    序列化 JavaScript 值并将序列化的表示形式添加到内部缓冲区。

    如果无法序列化 value,则会抛出错误。

    serializer.releaseBuffer()#

    返回存储的内部缓冲区。缓冲区释放后,不应再使用此序列化程序。如果之前的写入失败,调用此方法会导致未定义的行为。

    serializer.transferArrayBuffer(id, arrayBuffer)#

    标记 ArrayBuffer,其内容已带外传输。将序列化上下文中的相应 ArrayBuffer 传递给 deserializer.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 对象时,会调用此方法。它必须返回该对象的无符号 32 位整数 ID,如果此 SharedArrayBuffer 已经序列化,则使用相同的 ID。反序列化时,此 ID 将传递给 deserializer.transferArrayBuffer()

    如果无法序列化对象,则应抛出异常。

    此方法本身不在 Serializer 类中,但可以由子类提供。

    serializer._setTreatArrayBufferViewsAsHostObjects(flag)#

    指示是否将 TypedArrayDataView 对象视为宿主对象,即将它们传递给 serializer._writeHostObject()

    类:v8.Deserializer#

    new Deserializer(buffer)#

    创建一个新的 Deserializer 对象。

    deserializer.readHeader()#

    读取并验证头部(包括格式版本)。例如,可能会拒绝无效或不受支持的传输格式。在这种情况下,会抛出 Error

    deserializer.readValue()#

    从缓冲区反序列化 JavaScript 值并返回它。

    deserializer.transferArrayBuffer(id, arrayBuffer)#

    标记 ArrayBuffer,其内容已带外传输。将序列化上下文中的相应 ArrayBuffer 传递给 serializer.transferArrayBuffer()(或在 SharedArrayBuffer 的情况下,从 serializer._getSharedArrayBufferId() 返回 id)。

    deserializer.getWireFormatVersion()#

    读取底层的传输格式版本。可能主要对读取旧传输格式版本的遗留代码有用。在 .readHeader() 之前可能不会被调用。

    deserializer.readUint32()#

    读取原始 32 位无符号整数并返回。用于自定义 deserializer._readHostObject() 内部。

    deserializer.readUint64()#

    读取原始 64 位无符号整数,并将其作为数组 [hi, lo] 返回,包含两个 32 位无符号整数条目。用于自定义 deserializer._readHostObject() 内部。

    deserializer.readDouble()#

    读取 JS number 值。用于自定义 deserializer._readHostObject() 内部。

    deserializer.readRawBytes(length)#

    从反序列化程序的内部缓冲区读取原始字节。length 参数必须对应于传递给 serializer.writeRawBytes() 的缓冲区的长度。用于自定义 deserializer._readHostObject() 内部。

    deserializer._readHostObject()#

    调用此方法以读取某种宿主对象,即由原生 C++ 绑定创建的对象。如果无法反序列化数据,则应抛出适当的异常。

    此方法本身不在 Deserializer 类中,但可以由子类提供。

    类:v8.DefaultSerializer#

    Serializer 的子类,它将 TypedArray(特别是 Buffer)和 DataView 对象序列化为宿主对象,并且只存储它们所引用的底层 ArrayBuffer 的部分。

    类:v8.DefaultDeserializer#

    Deserializer 的子类,对应于由 DefaultSerializer 写入的格式。

    Promise 钩子#

    promiseHooks 接口可用于跟踪 Promise 生命周期事件。要跟踪 所有 异步活动,请参阅 async_hooks,它在内部使用此模块来产生 Promise 生命周期事件,此外还为其他异步资源产生事件。对于请求上下文管理,请参阅 AsyncLocalStorage

    import { promiseHooks } from 'node:v8';
    
    // There are four lifecycle events produced by promises:
    
    // The `init` event represents the creation of a promise. This could be a
    // direct creation such as with `new Promise(...)` or a continuation such
    // as `then()` or `catch()`. It also happens whenever an async function is
    // called or does an `await`. If a continuation promise is created, the
    // `parent` will be the promise it is a continuation from.
    function init(promise, parent) {
      console.log('a promise was created', { promise, parent });
    }
    
    // The `settled` event happens when a promise receives a resolution or
    // rejection value. This may happen synchronously such as when using
    // `Promise.resolve()` on non-promise input.
    function settled(promise) {
      console.log('a promise resolved or rejected', { promise });
    }
    
    // The `before` event runs immediately before a `then()` or `catch()` handler
    // runs or an `await` resumes execution.
    function before(promise) {
      console.log('a promise is about to call a then handler', { promise });
    }
    
    // The `after` event runs immediately after a `then()` handler runs or when
    // an `await` begins after resuming from another.
    function after(promise) {
      console.log('a promise is done calling a then handler', { promise });
    }
    
    // Lifecycle hooks may be started and stopped individually
    const stopWatchingInits = promiseHooks.onInit(init);
    const stopWatchingSettleds = promiseHooks.onSettled(settled);
    const stopWatchingBefores = promiseHooks.onBefore(before);
    const stopWatchingAfters = promiseHooks.onAfter(after);
    
    // Or they may be started and stopped in groups
    const stopHookSet = promiseHooks.createHook({
      init,
      settled,
      before,
      after,
    });
    
    // Trigger the hooks by using promises
    const promiseLog = (word) => Promise.resolve(word).then(console.log);
    promiseLog('Hello');
    promiseLog('World');
    
    // To stop a hook, call the function returned at its creation.
    stopWatchingInits();
    stopWatchingSettleds();
    stopWatchingBefores();
    stopWatchingAfters();
    stopHookSet();
    const { promiseHooks } = require('node:v8');
    
    // There are four lifecycle events produced by promises:
    
    // The `init` event represents the creation of a promise. This could be a
    // direct creation such as with `new Promise(...)` or a continuation such
    // as `then()` or `catch()`. It also happens whenever an async function is
    // called or does an `await`. If a continuation promise is created, the
    // `parent` will be the promise it is a continuation from.
    function init(promise, parent) {
      console.log('a promise was created', { promise, parent });
    }
    
    // The `settled` event happens when a promise receives a resolution or
    // rejection value. This may happen synchronously such as when using
    // `Promise.resolve()` on non-promise input.
    function settled(promise) {
      console.log('a promise resolved or rejected', { promise });
    }
    
    // The `before` event runs immediately before a `then()` or `catch()` handler
    // runs or an `await` resumes execution.
    function before(promise) {
      console.log('a promise is about to call a then handler', { promise });
    }
    
    // The `after` event runs immediately after a `then()` handler runs or when
    // an `await` begins after resuming from another.
    function after(promise) {
      console.log('a promise is done calling a then handler', { promise });
    }
    
    // Lifecycle hooks may be started and stopped individually
    const stopWatchingInits = promiseHooks.onInit(init);
    const stopWatchingSettleds = promiseHooks.onSettled(settled);
    const stopWatchingBefores = promiseHooks.onBefore(before);
    const stopWatchingAfters = promiseHooks.onAfter(after);
    
    // Or they may be started and stopped in groups
    const stopHookSet = promiseHooks.createHook({
      init,
      settled,
      before,
      after,
    });
    
    // Trigger the hooks by using promises
    const promisePrint = (word) => Promise.resolve(word).then(console.log);
    promisePrint('Hello');
    promisePrint('World');
    
    // To stop a hook, call the function returned at its creation.
    stopWatchingInits();
    stopWatchingSettleds();
    stopWatchingBefores();
    stopWatchingAfters();
    stopHookSet();
    

    promiseHooks.onInit(init)#

    init 钩子必须是普通函数。提供异步函数会抛出异常,因为它会导致无限微任务循环。

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

    promiseHooks.onSettled(settled)#

    settled 钩子必须是普通函数。提供异步函数会抛出异常,因为它会导致无限微任务循环。

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

    promiseHooks.onBefore(before)#

    before 钩子必须是普通函数。提供异步函数会抛出异常,因为它会导致无限微任务循环。

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

    promiseHooks.onAfter(after)#

    after 钩子必须是普通函数。提供异步函数会抛出异常,因为它会导致无限微任务循环。

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

    promiseHooks.createHook(callbacks)#

    钩子回调必须是普通函数。提供异步函数会抛出异常,因为它会导致无限微任务循环。

    注册函数以在每个 Promise 的不同生命周期事件中调用。

    回调 init()/before()/after()/settled() 会在 Promise 生命周期内的相应事件中被调用。

    所有回调都是可选的。例如,如果只需要跟踪 Promise 的创建,则只需传递 init 回调。所有可以传递给 callbacks 的函数的具体信息在 钩子回调 部分。

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

    钩子回调#

    Promise 生命周期中的关键事件已被归类为四个区域:创建 Promise、调用延续处理程序之前/之后或围绕 await,以及 Promise 解析或拒绝时。

    虽然这些钩子类似于 async_hooks 的钩子,但它们缺乏 destroy 钩子。其他类型的异步资源通常表示具有明确“已关闭”状态的套接字或文件描述符,以表达 destroy 生命周期事件,而只要代码仍然可以访问它们,Promise 就保持可用。垃圾回收跟踪用于使 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() 的情况下,这可能会同步发生。

    启动快照 API#

    v8.startupSnapshot 接口可用于为自定义启动快照添加序列化和反序列化钩子。

    $ node --snapshot-blob snapshot.blob --build-snapshot entry.js
    # This launches a process with the snapshot
    $ node --snapshot-blob snapshot.blob
    

    在上面的示例中,entry.js 可以使用 v8.startupSnapshot 接口中的方法来指定如何在序列化期间保存快照中自定义对象的信息,以及如何在反序列化快照期间使用这些信息来同步这些对象。例如,如果 entry.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();
    
      // Reading a series of files from directory and store them into storage.
      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 here is where the snapshot script is placed
    // during snapshot building time.
    const shelf = new BookShelf(__dirname, [
      'book1.en_US.txt',
      'book1.es_ES.txt',
      'book2.zh_CN.txt',
    ]);
    
    assert(v8.startupSnapshot.isBuildingSnapshot());
    // On snapshot serialization, compress the books to reduce size.
    v8.startupSnapshot.addSerializeCallback(BookShelf.compressAll, shelf);
    // On snapshot deserialization, decompress the books.
    v8.startupSnapshot.addDeserializeCallback(BookShelf.decompressAll, shelf);
    v8.startupSnapshot.setDeserializeMainFunction((shelf) => {
      // process.env and process.argv are refreshed during snapshot
      // deserialization.
      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

    $ BOOK_LANG=es_ES node --snapshot-blob snapshot.blob book1
    # Prints content of book1.es_ES.txt deserialized from the snapshot.
    

    目前,从用户态快照反序列化的应用程序无法再次创建快照,因此这些 API 仅适用于不是从用户态快照反序列化的应用程序。

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

    • callback <Function> 在序列化之前调用的回调。
    • data <any> 在调用时将传递给 callback 的可选数据。

    添加一个回调,该回调将在 Node.js 实例即将被序列化为快照并退出时被调用。这可用于释放不应或无法序列化的资源,或者将用户数据转换为更适合序列化的形式。

    回调按添加顺序运行。

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

    • callback <Function> 在反序列化快照后调用的回调。
    • data <any> 在调用时将传递给 callback 的可选数据。

    添加一个回调,该回调将在 Node.js 实例从快照反序列化时被调用。callbackdata(如果提供)将被序列化到快照中,它们可用于重新初始化应用程序的状态,或重新获取应用程序在从快照重新启动时所需的资源。

    回调按添加顺序运行。

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

    • callback <Function> 在快照反序列化后作为入口点调用的回调。
    • data <any> 在调用时将传递给 callback 的可选数据。

    这设置了 Node.js 应用程序从快照反序列化时的入口点。这只能在快照构建脚本中调用一次。如果调用,反序列化的应用程序不再需要额外的入口点脚本来启动,并且将直接调用回调以及反序列化的数据(如果提供);否则,仍需为反序列化的应用程序提供入口点脚本。

    v8.startupSnapshot.isBuildingSnapshot()#

    如果运行 Node.js 实例是为了构建快照,则返回 true。

    类:v8.GCProfiler#

    此 API 在当前线程中收集 GC 数据。

    new v8.GCProfiler()#

    创建 v8.GCProfiler 类的新实例。此 API 支持 using 语法。

    profiler.start()#

    开始收集 GC 数据。

    profiler.stop()#

    停止收集 GC 数据并返回一个对象。对象的内容如下。

    {
      "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
    }
    

    这是一个示例。

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

    profiler[Symbol.dispose]()#

    停止收集 GC 数据,并丢弃该 profile。

    类:SyncCPUProfileHandle#

    syncCpuProfileHandle.stop()#

    停止收集 profile 并返回 profile 数据。

    syncCpuProfileHandle[Symbol.dispose]()#

    停止收集 profile,profile 将被丢弃。

    类:CPUProfileHandle#

    cpuProfileHandle.stop()#

    停止收集 profile,然后返回一个 Promise,该 Promise 以错误或 profile 数据完成。

    cpuProfileHandle[Symbol.asyncDispose]()#

    停止收集 profile,profile 将被丢弃。

    类:HeapProfileHandle#

    heapProfileHandle.stop()#

    停止收集 profile,然后返回一个 Promise,该 Promise 以错误或 profile 数据完成。

    heapProfileHandle[Symbol.asyncDispose]()#

    停止收集 profile,profile 将被丢弃。

    v8.isStringOneByteRepresentation(content)#

    V8 仅支持 Latin-1/ISO-8859-1UTF16 作为字符串的底层表示。如果 content 使用 Latin-1/ISO-8859-1 作为底层表示,此函数将返回 true;否则,它返回 false。

    如果此方法返回 false,并不意味着字符串包含不在 Latin-1/ISO-8859-1 中的某些字符。有时 Latin-1 字符串也可以表示为 UTF16

    import { isStringOneByteRepresentation } from 'node:v8';
    import { Buffer } from 'node:buffer';
    
    const Encoding = {
      latin1: 1,
      utf16le: 2,
    };
    const buffer = Buffer.alloc(100);
    function writeString(input) {
      if (isStringOneByteRepresentation(input)) {
        console.log(`input: '${input}'`);
        buffer.writeUint8(Encoding.latin1);
        buffer.writeUint32LE(input.length, 1);
        buffer.write(input, 5, 'latin1');
        console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`);
      } else {
        console.log(`input: '${input}'`);
        buffer.writeUint8(Encoding.utf16le);
        buffer.writeUint32LE(input.length * 2, 1);
        buffer.write(input, 5, 'utf16le');
        console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`);
      }
    }
    writeString('hello');
    writeString('你好');
    const { isStringOneByteRepresentation } = require('node:v8');
    const { Buffer } = require('node:buffer');
    
    const Encoding = {
      latin1: 1,
      utf16le: 2,
    };
    const buffer = Buffer.alloc(100);
    function writeString(input) {
      if (isStringOneByteRepresentation(input)) {
        console.log(`input: '${input}'`);
        buffer.writeUint8(Encoding.latin1);
        buffer.writeUint32LE(input.length, 1);
        buffer.write(input, 5, 'latin1');
        console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`);
      } else {
        console.log(`input: '${input}'`);
        buffer.writeUint8(Encoding.utf16le);
        buffer.writeUint32LE(input.length * 2, 1);
        buffer.write(input, 5, 'utf16le');
        console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`);
      }
    }
    writeString('hello');
    writeString('你好');
    

    v8.startCpuProfile()#

    • 返回:{SyncCPUProfileHandle}

    开始 CPU profile 然后返回 SyncCPUProfileHandle 对象。此 API 支持 using 语法。

    const handle = v8.startCpuProfile();
    const profile = handle.stop();
    console.log(profile);