V8
History
node:v8 模块暴露了特定于内置于 Node.js 二进制文件中的 V8 版本的 API。可以使用以下方式访问它:
import v8 from 'node:v8';
const v8 = require('node:v8');
v8.cachedDataVersionTag(): void
- 返回:
integer
返回一个整数,表示源自 V8 版本、命令行标志和检测到的 CPU 特性的版本标签。这对于确定 vm.Script cachedData 缓冲区是否与此 V8 实例兼容很有用。
console.log(v8.cachedDataVersionTag()); // 3947234607 // v8.cachedDataVersionTag() 返回的值源自 V8 // 版本、命令行标志和检测到的 CPU 特性。测试该值 // 确实在切换标志时更新。 v8.setFlagsFromString('--allow_natives_syntax'); console.log(v8.cachedDataVersionTag()); // 183726201
v8.getHeapCodeStatistics(): void
- 返回:
Object
获取堆中代码及其元数据的统计信息,参见 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?): void
生成当前 V8 堆的快照,并返回一个 Readable 流,可用于读取 JSON 序列化表示。此 JSON 流格式旨在与 Chrome DevTools 等工具一起使用。JSON 架构未记录文档且特定于 V8 引擎。因此,架构可能会随 V8 版本的变化而变化。
创建堆快照所需的内存大约是创建快照时堆大小的两倍。这导致存在 OOM killer 终止进程的风险。
生成快照是一个同步操作,会根据堆大小阻塞事件循环一段时间。
// 将堆快照打印到控制台 import { getHeapSnapshot } from 'node:v8'; import process from 'node:process'; const stream = getHeapSnapshot(); stream.pipe(process.stdout);
// 将堆快照打印到控制台 const v8 = require('node:v8'); const stream = v8.getHeapSnapshot(); stream.pipe(process.stdout);
v8.getHeapSpaceStatistics(): void
- 返回:
Object[]
返回有关 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(): void
- 返回:
Object
返回一个包含以下属性的对象:
numbernumbernumbernumbernumbernumbernumbernumbernumbernumbernumbernumbernumbernumbernumbertotal_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?): void
使用 V8 CollectStatistics() 函数检索有关内存消耗和
利用率的 CppHeap 统计信息,该函数
可能会随 V8 版本的变化而变化。
它返回一个结构类似于
cppgc::HeapStatistics 对象的对象。有关对象属性的更多信息,请参阅 V8 文档。
// 详细 ({ 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', });
// 简要 ({ committed_size_bytes: 131072, resident_size_bytes: 131072, used_size_bytes: 128864, space_statistics: [], type_names: [], detail_level: 'brief', });
v8.queryObjects(ctor, options?): void
这类似于 Chromium DevTools 控制台提供的 [queryObjects() 控制台 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' })); // 请注意,当存在继承自构造函数的子类时, // 该构造函数也会出现在子类原型链中,因此子类的原型也会被 // 包含在结果中。 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' })); // 请注意,当存在继承自构造函数的子类时, // 该构造函数也会出现在子类原型链中,因此子类的原型也会被 // 包含在结果中。 console.log(queryObjects(A)); // 3 // [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ] console.log(queryObjects(A, { format: 'summary' }));
v8.setFlagsFromString(flags): void
stringv8.setFlagsFromString() 方法可用于以编程方式设置 V8 命令行标志。应谨慎使用此方法。在虚拟机启动后更改设置可能会导致不可预测的行为,包括崩溃和数据丢失;或者可能根本不起作用。
某版本 Node.js 可用的 V8 选项可通过运行 node --v8-options 确定。
用法:
import { setFlagsFromString } from 'node:v8'; import { setInterval } from 'node:timers'; // 设置 Flags 以追踪垃圾回收事件 setFlagsFromString('--trace-gc'); // 通过使用一些内存来触发 GC 事件 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); // 1.5 秒后设置 Flags 以停止追踪 GC 事件 setTimeout(() => { setFlagsFromString('--notrace-gc'); console.log('\nStopped tracing!\n'); }, 1500); // 2.5 秒后完全停止触发 GC 事件 setTimeout(() => { clearInterval(interval); }, 2500);
const { setFlagsFromString } = require('node:v8'); const { setInterval } = require('node:timers'); // 设置 Flags 以追踪垃圾回收事件 setFlagsFromString('--trace-gc'); // 通过使用一些内存来触发 GC 事件 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); // 1.5 秒后设置 Flags 以停止追踪 GC 事件 setTimeout(() => { console.log('\nStopped tracing!\n'); setFlagsFromString('--notrace-gc'); }, 1500); // 2.5 秒后完全停止触发 GC 事件 setTimeout(() => { clearInterval(interval); }, 2500);
v8.stopCoverage(): void
v8.stopCoverage() 方法允许用户停止由 NODE_V8_COVERAGE 启动的覆盖率收集,以便 V8 可以释放执行计数记录并优化代码。如果用户想要按需收集覆盖率,可以与 v8.takeCoverage() 结合使用。
v8.takeCoverage(): void
v8.takeCoverage() 方法允许用户按需将由 NODE_V8_COVERAGE 启动的覆盖率写入磁盘。此方法可在进程生命周期内多次调用。每次调用时,执行计数器将被重置,并且新的覆盖率报告将被写入由 NODE_V8_COVERAGE 指定的目录。
除非在进程退出之前调用 v8.stopCoverage(),否则当进程即将退出时,仍会将最后一次覆盖率写入磁盘。
v8.writeHeapSnapshot(filename?, options?): void
生成当前 V8 堆的快照并将其写入 JSON 文件。此文件旨在与 Chrome DevTools 等工具一起使用。JSON 模式未公开文档且特定于 V8 引擎,并且可能随 V8 版本的变化而变化。
堆快照特定于单个 V8 隔离区。在使用 [工作线程][] 时,从主线程生成的堆快照不包含任何关于工作线程的信息,反之亦然。
创建堆快照需要大约创建时堆大小两倍的内存。这会导致 OOM 杀手终止进程的风险。
生成快照是一个同步操作,会根据堆大小阻塞事件循环一段时间。
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}`); // 现在获取主线程的堆转储。 console.log(`main thread heapdump: ${writeHeapSnapshot()}`); }); // 告诉 worker 创建堆转储。 worker.postMessage('heapdump'); } else { parentPort.once('message', (message) => { if (message === 'heapdump') { // 为 worker 生成堆转储 // 并将文件名返回给父线程。 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}`); // 现在获取主线程的堆转储。 console.log(`main thread heapdump: ${writeHeapSnapshot()}`); }); // 告诉 worker 创建堆转储。 worker.postMessage('heapdump'); } else { parentPort.once('message', (message) => { if (message === 'heapdump') { // 为 worker 生成堆转储 // 并将文件名返回给父线程。 parentPort.postMessage(writeHeapSnapshot()); } }); }
选项
History
标记 API 为稳定版。
integer如果已通过命令行设置 --heapsnapshot-near-heap-limit 或此 API 被调用超过一次,则此 API 为空操作。limit 必须是正整数。有关更多信息,请参阅 --heapsnapshot-near-heap-limit。
序列化 API 提供了序列化 JavaScript 值的方法, 这种方式与 [HTML 结构化克隆算法][] 兼容。
该格式是向后兼容的(即可以安全地存储到磁盘)。 相等的 JavaScript 值可能会产生不同的序列化输出。
结构化克隆序列化器
History
any使用 DefaultSerializer 将 value 序列化为缓冲区。
当尝试序列化一个需要比 buffer.constants.MAX_LENGTH 更大的缓冲区的巨大对象时,
将抛出 ERR_BUFFER_TOO_LARGE。
结构化克隆反序列化器
History
Buffer | TypedArray | DataViewserialize()
返回的缓冲区。使用 DefaultDeserializer 及默认选项从缓冲区读取 JS 值。
类:结构化克隆序列化器
History
创建一个新的 Serializer 对象。
写入一个头,其中包括序列化格式版本。
any序列化一个 JavaScript 值并将序列化表示添加到内部缓冲区。
如果 value 无法序列化,这将抛出错误。
- 返回:
Buffer
返回存储的内部缓冲区。一旦缓冲区被释放,此序列化器不应再被使用。 如果之前的写入失败,调用此方法会导致未定义的行为。
integerArrayBufferArrayBuffer
实例。标记一个 ArrayBuffer 为其内容已带外传输。
在反序列化上下文中将相应的 ArrayBuffer 传递给 deserializer.transferArrayBuffer()。
integer写入一个原始 32 位无符号整数。
用于自定义 serializer._writeHostObject() 内部。
写入一个原始 64 位无符号整数,分为高 32 位和低 32 位部分。
用于自定义 serializer._writeHostObject() 内部。
number写入一个 JS number 值。
用于自定义 serializer._writeHostObject() 内部。
Buffer | TypedArray | DataView将原始字节写入序列化器的内部缓冲区。反序列化器将需要一种方法来计算缓冲区的长度。
用于自定义 serializer._writeHostObject() 内部。
Object调用此方法是为了写入某种宿主对象,即由原生 C++ 绑定创建的对象。
如果无法序列化 object,应抛出合适的异常。
此方法不存在于 Serializer 类本身上,但可以由子类提供。
string调用此方法是为了生成当对象无法被克隆时将抛出的错误对象。
此方法默认为 Error 构造函数,可以在子类上被重写。
SharedArrayBuffer当序列化器将要序列化一个 SharedArrayBuffer 对象时,调用此方法。
它必须返回该对象的无符号 32 位整数 ID,如果此 SharedArrayBuffer 已经被序列化,则使用相同的 ID。
反序列化时,此 ID 将被传递给 deserializer.transferArrayBuffer()。
如果对象无法被序列化,应抛出异常。
此方法不存在于 Serializer 类本身上,但可以由子类提供。
serializer._setTreatArrayBufferViewsAsHostObjects(flag): void
booleanfalse指示是否将 TypedArray 和 DataView 对象视为宿主对象,即传递给 serializer._writeHostObject()。
类:结构化克隆反序列化器
History
Buffer | TypedArray | DataViewserializer.releaseBuffer()
返回的缓冲区。创建一个新的 Deserializer 对象。
读取并验证头(包括格式版本)。
例如,可能会拒绝无效或不支持的线路格式。在这种情况下,将抛出 Error。
从缓冲区反序列化一个 JavaScript 值并返回它。
integerArrayBuffer | SharedArrayBufferArrayBuffer
实例。标记一个 ArrayBuffer 为其内容已带外传输。
在序列化上下文中将相应的 ArrayBuffer 传递给 serializer.transferArrayBuffer()(如果是 SharedArrayBuffer,则从 serializer._getSharedArrayBufferId() 返回 id)。
- 返回:
integer
读取底层线路格式版本。可能主要对读取旧线路格式版本的遗留代码有用。
不能在 .readHeader() 之前调用。
- 返回:
integer
读取一个原始 32 位无符号整数并返回它。
用于自定义 deserializer._readHostObject() 内部。
- 返回:
integer[]
读取一个原始 64 位无符号整数并作为数组 [hi, lo] 返回,包含两个 32 位无符号整数条目。
用于自定义 deserializer._readHostObject() 内部。
- 返回:
number
读取一个 JS number 值。
用于自定义 deserializer._readHostObject() 内部。
integer从反序列化器的内部缓冲区读取原始字节。length 参数必须对应于传递给 serializer.writeRawBytes() 的缓冲区长度。
用于自定义 deserializer._readHostObject() 内部。
调用此方法是为了读取某种宿主对象,即由原生 C++ 绑定创建的对象。 如果无法反序列化数据,应抛出合适的异常。
此方法不存在于 Deserializer 类本身上,但可以由子类提供。
类:结构化克隆共享数组缓冲区
History
Serializer 的子类,将 TypedArray(特别是 Buffer)和 DataView 对象序列化为宿主对象,
并且只存储它们所引用的底层 ArrayBuffer 的部分。
类:结构化克隆反序列化共享数组缓冲区
History
对应于 DefaultSerializer 写入格式的 Deserializer 子类。
promiseHooks 接口可用于跟踪 promise 生命周期事件。
要跟踪 所有 异步活动,请参阅 async_hooks,它在内部使用此模块来产生 promise 生命周期事件以及其他异步资源的事件。
对于请求上下文管理,请参阅 AsyncLocalStorage。
import { promiseHooks } from 'node:v8'; // Promise 会产生四个生命周期事件: // `init` 事件代表 promise 的创建。这可以是直接创建,例如使用 `new Promise(...)`, // 也可以是延续,例如使用 `then()` 或 `catch()`。每当调用异步函数或执行 `await` 时也会发生。 // 如果创建了延续 promise,`parent` 将是它所延续的 promise。 function init(promise, parent) { console.log('一个 promise 被创建了', { promise, parent }); } // 当 promise 收到决议或拒绝值时,会发生 `settled` 事件。 // 这可能同步发生,例如当在非 promise 输入上使用 `Promise.resolve()` 时。 function settled(promise) { console.log('一个 promise 已决议或被拒绝', { promise }); } // `before` 事件运行于 `then()` 或 `catch()` 处理程序运行之前或 `await` 恢复执行之前。 function before(promise) { console.log('一个 promise 即将调用 then 处理程序', { promise }); } // `after` 事件运行于 `then()` 处理程序运行之后,或当一个 `await` 在从另一个恢复后开始时。 function after(promise) { console.log('一个 promise 已完成调用 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, }); // 通过使用 promise 触发钩子 const promisePrint = (word) => Promise.resolve(word).then(console.log); promisePrint('Hello'); promisePrint('World'); // 要停止钩子,调用其创建时返回的函数。 stopWatchingInits(); stopWatchingSettleds(); stopWatchingBefores(); stopWatchingAfters(); stopHookSet();
const { promiseHooks } = require('node:v8'); // Promise 会产生四个生命周期事件: // `init` 事件代表 promise 的创建。这可以是直接创建,例如使用 `new Promise(...)`, // 也可以是延续,例如使用 `then()` 或 `catch()`。每当调用异步函数或执行 `await` 时也会发生。 // 如果创建了延续 promise,`parent` 将是它所延续的 promise。 function init(promise, parent) { console.log('一个 promise 被创建了', { promise, parent }); } // 当 promise 收到决议或拒绝值时,会发生 `settled` 事件。 // 这可能同步发生,例如当在非 promise 输入上使用 `Promise.resolve()` 时。 function settled(promise) { console.log('一个 promise 已决议或被拒绝', { promise }); } // `before` 事件运行于 `then()` 或 `catch()` 处理程序运行之前或 `await` 恢复执行之前。 function before(promise) { console.log('一个 promise 即将调用 then 处理程序', { promise }); } // `after` 事件运行于 `then()` 处理程序运行之后,或当一个 `await` 在从另一个恢复后开始时。 function after(promise) { console.log('一个 promise 已完成调用 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, }); // 通过使用 promise 触发钩子 const promisePrint = (word) => Promise.resolve(word).then(console.log); promisePrint('Hello'); promisePrint('World'); // 要停止钩子,调用其创建时返回的函数。 stopWatchingInits(); stopWatchingSettleds(); stopWatchingBefores(); stopWatchingAfters(); stopHookSet();
promiseHooks.onInit(init): void
Functioninit
回调][]。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): void
Functionsettled
回调][]。settled 钩子必须是普通函数。提供异步函数将抛出异常,因为它会产生无限的微任务循环。
import { promiseHooks } from 'node:v8'; const stop = promiseHooks.onSettled((promise) => {});
const { promiseHooks } = require('node:v8'); const stop = promiseHooks.onSettled((promise) => {});
promiseHooks.onBefore(before): void
Functionbefore
回调][]。before 钩子必须是普通函数。提供异步函数将抛出异常,因为它会产生无限的微任务循环。
import { promiseHooks } from 'node:v8'; const stop = promiseHooks.onBefore((promise) => {});
const { promiseHooks } = require('node:v8'); const stop = promiseHooks.onBefore((promise) => {});
promiseHooks.onAfter(after): void
Functionafter
回调][]。after 钩子必须是普通函数。提供异步函数将抛出异常,因为它会产生无限的微任务循环。
import { promiseHooks } from 'node:v8'; const stop = promiseHooks.onAfter((promise) => {});
const { promiseHooks } = require('node:v8'); const stop = promiseHooks.onAfter((promise) => {});
promiseHooks.createHook(callbacks): void
钩子回调必须是普通函数。提供异步函数将抛出异常,因为它会产生无限的微任务循环。
注册函数以便为每个 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 钩子。
其他类型的异步资源通常代表套接字或文件描述符,它们具有独特的 "closed" 状态来表达 destroy 生命周期事件,
而 promise 只要代码仍能访问它们就保持可用。
垃圾回收跟踪用于使 promise 适应 async_hooks 事件模型,然而这种跟踪非常昂贵,并且它们甚至不一定最终会被垃圾回收。
因为 promise 是异步资源,其生命周期通过 promise 钩子机制跟踪,
所以 init()、before()、after() 和 settled() 回调 不能 是异步函数,
因为它们会创建更多 promise,从而产生无限循环。
虽然此 API 用于将 promise 事件馈送到 async_hooks,但两者之间的顺序是未定义的。
两个 API 都是多租户的,因此可能以任何顺序相互产生事件。
init
init(promise, parent): void
当构造 promise 时调用。这 不 意味着相应的 before/after 事件会发生,只意味着存在可能性。
如果创建了一个 promise 但从未获得延续,就会发生这种情况。
before(promise): void
Promise在 promise 延续执行之前调用。这可以是 then()、catch() 或 finally() 处理程序的形式,或者是 await 恢复。
before 回调将被调用 0 到 N 次。如果从未对 promise 进行延续,before 回调通常将被调用 0 次。
如果从同一个 promise 进行了许多延续,before 回调可能会被调用多次。
after(promise): void
Promise在 promise 延续执行后立即调用。这可能是在 then()、catch() 或 finally() 处理程序之后,
或在一个 await 之后的另一个 await 之前。
settled(promise): void
Promise当 promise 收到决议或拒绝值时调用。在 Promise.resolve() 或 Promise.reject() 的情况下,这可能同步发生。
启动快照 API
History
将 API 标记为稳定。
v8.startupSnapshot 接口可用于为自定义启动快照添加序列化和反序列化钩子。
$ node --snapshot-blob snapshot.blob --build-snapshot entry.js # 这将启动一个带有快照的进程 $ node --snapshot-blob snapshot.blob
在上面的示例中,entry.js 可以使用 v8.startupSnapshot 接口中的方法来指定在序列化期间如何保存快照中自定义对象的信息,以及在快照反序列化期间如何使用这些信息来同步这些对象。例如,如果 entry.js 包含以下脚本:
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(); // 从目录读取一系列文件并将其存储到 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 这里是快照脚本在快照构建期间放置的位置。 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[2]; const name = `${book}.${lang}.txt`; console.log(shelf.storage.get(name)); }, shelf);
生成的二进制文件将在启动期间打印从快照反序列化的数据,使用启动进程的刷新后的 process.env 和 process.argv:
$ BOOK_LANG=es_ES node --snapshot-blob snapshot.blob book1 # 打印从快照反序列化的 book1.es_ES.txt 的内容。
目前,从用户态快照反序列化的应用程序无法再次被快照化,因此这些 API 仅适用于未从用户态快照反序列化的应用程序。
v8.startupSnapshot.addSerializeCallback(callback, data?): void
添加一个回调,当 Node.js 实例即将被序列化为快照并退出时调用。这可用于释放不应或无法序列化的资源,或将用户数据转换为更适合序列化的形式。
回调按添加的顺序运行。
v8.startupSnapshot.addDeserializeCallback(callback, data?): void
添加一个回调,当 Node.js 实例从快照反序列化时调用。callback 和 data(如果提供)将被序列化到快照中,它们可用于重新初始化应用程序的状态,或在应用程序从快照重新启动时重新获取应用程序所需的资源。
回调按添加的顺序运行。
v8.startupSnapshot.setDeserializeMainFunction(callback, data?): void
这设置了 Node.js 应用程序从快照反序列化时的入口点。这在快照构建脚本中只能调用一次。如果调用了,反序列化的应用程序不再需要额外的入口点脚本来启动,将简单地调用回调以及反序列化的数据(如果提供),否则仍然需要为反序列化的应用程序提供入口点脚本。
v8.startupSnapshot.isBuildingSnapshot(): void
- 返回:
boolean
如果运行 Node.js 实例是为了构建快照,则返回 true。
类:v8.GCProfiler
History
此 API 收集当前线程中的 GC 数据。
new v8.GCProfiler(): void
创建 v8.GCProfiler 类的新实例。
此 API 支持 using 语法。
profiler.start(): void
开始收集 GC 数据。
profiler.stop(): void
停止收集 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](): void
停止收集 GC 数据,并丢弃配置文件。
类:SyncCPUProfileHandle
History
syncCpuProfileHandle.stop(): void
- 返回:
string
停止收集配置文件并返回配置文件数据。
syncCpuProfileHandle[Symbol.dispose](): void
停止收集配置文件,配置文件将被丢弃。
类:SyncHeapProfileHandle
History
稳定性:1 - 实验性
syncHeapProfileHandle.stop(): void
- 返回:
string
停止收集配置文件并返回配置文件数据。
syncHeapProfileHandle[Symbol.dispose](): void
停止收集配置文件,配置文件将被丢弃。
类:CPUProfileHandle
History
cpuProfileHandle.stop(): void
- 返回:
Promise
停止收集配置文件,然后返回一个 Promise,该 Promise 将以错误或配置文件数据兑现。
cpuProfileHandle[Symbol.asyncDispose](): void
- 返回:
Promise
停止收集配置文件,配置文件将被丢弃。
类:HeapProfileHandle
History
heapProfileHandle.stop(): void
- 返回:
Promise
停止收集配置文件,然后返回一个 Promise,该 Promise 将以错误或配置文件数据兑现。
heapProfileHandle[Symbol.asyncDispose](): void
- 返回:
Promise
停止收集配置文件,配置文件将被丢弃。
v8.isStringOneByteRepresentation(content): void
stringV8 仅支持 Latin-1/ISO-8859-1 和 UTF16 作为字符串的底层表示形式。
如果 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(options?): void
启动 CPU 剖析然后返回一个 SyncCPUProfileHandle 对象。
此 API 支持 using 语法。
const handle = v8.startCpuProfile({ sampleInterval: 1, maxBufferSize: 10_000 }); const profile = handle.stop(); console.log(profile);
v8.startHeapProfile(options?): void
开始堆分析,然后返回一个 SyncHeapProfileHandle 对象。
此 API 支持 using 语法。
const v8 = require('node:v8'); const handle = v8.startHeapProfile(); const profile = handle.stop(); console.log(profile);
import v8 from 'node:v8'; const handle = v8.startHeapProfile(); const profile = handle.stop(); console.log(profile);
使用自定义参数:
const v8 = require('node:v8'); const handle = v8.startHeapProfile({ sampleInterval: 1024, stackDepth: 8, forceGC: true, includeObjectsCollectedByMajorGC: true, }); const profile = handle.stop(); console.log(profile);
import v8 from 'node:v8'; const handle = v8.startHeapProfile({ sampleInterval: 1024, stackDepth: 8, forceGC: true, includeObjectsCollectedByMajorGC: true, }); const profile = handle.stop(); console.log(profile);