V8
源码: lib/v8.js
node:v8
模块公开了特定于构建到 Node.js 二进制文件中的 V8 版本的 API。 可以使用以下方式访问它:
const v8 = require('node:v8');
v8.cachedDataVersionTag()
新增于: v8.0.0
- 返回: <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()
新增于: v12.8.0
- 返回: <Object>
获取有关堆中代码及其元数据的统计信息,请参阅 V8 GetHeapCodeAndMetadataStatistics
API。 返回一个具有以下属性的对象:
code_and_metadata_size
<number>bytecode_and_metadata_size
<number>external_script_source_size
<number>cpu_profiler_metadata_size
<number>
{
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>返回值: <stream.Readable> 一个 Readable 流,包含 V8 堆快照。
生成当前 V8 堆的快照,并返回一个 Readable Stream,该流可用于读取 JSON 序列化表示。 此 JSON 流格式旨在与 Chrome DevTools 等工具一起使用。 JSON 模式未进行文档化,并且特定于 V8 引擎。 因此,该模式可能会在 V8 的不同版本之间发生变化。
创建堆快照所需的内存约为创建快照时堆大小的两倍。 这会导致 OOM killer 终止进程的风险。
生成快照是一个同步操作,会阻塞事件循环,持续时间取决于堆大小。
// 将堆快照打印到控制台
const v8 = require('node:v8');
const stream = v8.getHeapSnapshot();
stream.pipe(process.stdout);
v8.getHeapSpaceStatistics()
[历史]
版本 | 变更 |
---|---|
v7.5.0 | 支持超出 32 位无符号整数范围的值。 |
v6.0.0 | 添加于: v6.0.0 |
- 返回值: <Object[]>
返回有关 V8 堆空间的统计信息,即组成 V8 堆的段。 既不能保证堆空间的排序,也不能保证堆空间的可用性,因为这些统计信息是通过 V8 的 GetHeapSpaceStatistics
函数提供的,并且可能随 V8 版本的不同而变化。
返回的值是包含以下属性的对象数组:
space_name
<string>space_size
<number>space_used_size
<number>space_available_size
<number>physical_space_size
<number>
[
{
"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.0 | 支持超出 32 位无符号整数范围的值。 |
v7.2.0 | 添加了 malloced_memory 、peak_malloced_memory 和 does_zap_garbage 。 |
v1.0.0 | 添加于: v1.0.0 |
- 返回: <Object>
返回一个包含以下属性的对象:
total_heap_size
<number>total_heap_size_executable
<number>total_physical_size
<number>total_available_size
<number>used_heap_size
<number>heap_size_limit
<number>malloced_memory
<number>peak_malloced_memory
<number>does_zap_garbage
<number>number_of_native_contexts
<number>number_of_detached_contexts
<number>total_global_handles_size
<number>used_global_handles_size
<number>external_memory
<number>
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_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
ctor
<Function> 构造函数,可用于在原型链上搜索,以便过滤堆中的目标对象。options
<undefined> | <Object>format
<string> 如果是'count'
,则返回匹配对象的数量。 如果是'summary'
,则返回包含匹配对象摘要字符串的数组。
返回: {number|Array
这类似于 Chromium DevTools 控制台提供的 queryObjects()
console 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)
Added in: v1.0.0
flags
<string>
v8.setFlagsFromString()
方法可用于以编程方式设置 V8 命令行标志。应谨慎使用此方法。在 VM 启动后更改设置可能会导致不可预测的行为,包括崩溃和数据丢失;或者可能根本不起作用。
可以通过运行 node --v8-options
来确定 Node.js 版本的可用 V8 选项。
用法:
// 将 GC 事件打印到 stdout 一分钟。
const v8 = require('node:v8');
v8.setFlagsFromString('--trace_gc');
setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
v8.stopCoverage()
Added in: v15.1.0, v14.18.0, v12.22.0
v8.stopCoverage()
方法允许用户停止由 NODE_V8_COVERAGE
启动的覆盖率收集,以便 V8 可以释放执行计数记录并优化代码。如果用户希望按需收集覆盖率,则可以与 v8.takeCoverage()
结合使用。
v8.takeCoverage()
Added in: 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,当从主 Node.js 线程或工作线程的 ID 调用writeHeapSnapshot()
时,{thread_id}
将为0
。options
<Object>返回: <string> 快照保存的文件名。
生成当前 V8 堆的快照并将其写入 JSON 文件。此文件旨在与 Chrome DevTools 等工具一起使用。JSON 模式没有文档记录,并且特定于 V8 引擎,并且可能因 V8 版本而异。
堆快照特定于单个 V8 隔离。当使用工作线程时,从主线程生成的堆快照将不包含有关工作线程的任何信息,反之亦然。
创建堆快照需要大约两倍于创建快照时堆大小的内存。这导致 OOM killer 终止进程的风险。
生成快照是一个同步操作,会阻塞事件循环,持续时间取决于堆大小。
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.postMessage('heapdump');
} else {
parentPort.once('message', (message) => {
if (message === 'heapdump') {
// 为工作线程生成一个堆转储
// 并将文件名返回给父进程。
parentPort.postMessage(writeHeapSnapshot());
}
});
}
v8.setHeapSnapshotNearHeapLimit(limit)
新增于: v18.10.0, v16.18.0
limit
<integer>
如果命令行已设置 --heapsnapshot-near-heap-limit
,或者该 API 被多次调用,则该 API 不执行任何操作。 limit
必须是一个正整数。 有关更多信息,请参见 --heapsnapshot-near-heap-limit
。
序列化 API
序列化 API 提供了一种以与 HTML 结构化克隆算法 兼容的方式序列化 JavaScript 值的方法。
该格式是向后兼容的(即,可以安全地存储到磁盘)。 相等的 JavaScript 值可能会导致不同的序列化输出。
v8.serialize(value)
新增于: v8.0.0
使用 DefaultSerializer
将 value
序列化为缓冲区。
当尝试序列化一个需要大于 buffer.constants.MAX_LENGTH
的缓冲区的巨大对象时,将抛出 ERR_BUFFER_TOO_LARGE
。
v8.deserialize(buffer)
新增于: v8.0.0
buffer
<Buffer> | <TypedArray> | <DataView> 由serialize()
返回的缓冲区。
使用具有默认选项的 DefaultDeserializer
从缓冲区读取 JS 值。
类: v8.Serializer
添加于: v8.0.0
new Serializer()
创建一个新的 Serializer
对象。
serializer.writeHeader()
写出一个头部,其中包含序列化格式的版本。
serializer.writeValue(value)
value
<any>
序列化一个 JavaScript 值,并将序列化的表示添加到内部缓冲区。
如果 value
无法被序列化,则会抛出一个错误。
serializer.releaseBuffer()
- 返回: <Buffer>
返回存储的内部缓冲区。一旦缓冲区被释放,就不应该再使用此序列化器。如果之前的写入操作失败,调用此方法会导致未定义的行为。
serializer.transferArrayBuffer(id, arrayBuffer)
id
<integer> 一个 32 位的无符号整数。arrayBuffer
<ArrayBuffer> 一个ArrayBuffer
实例。
标记一个 ArrayBuffer
,表明其内容已带外传输。在反序列化上下文中,将相应的 ArrayBuffer
传递给 deserializer.transferArrayBuffer()
。
serializer.writeUint32(value)
value
<integer>
写入一个原始的 32 位无符号整数。用于自定义的 serializer._writeHostObject()
内部。
serializer.writeUint64(hi, lo)
写入一个原始的 64 位无符号整数,拆分为高 32 位和低 32 位部分。用于自定义的 serializer._writeHostObject()
内部。
serializer.writeDouble(value)
value
<number>
写入一个 JS number
值。 用于自定义 serializer._writeHostObject()
内部。
serializer.writeRawBytes(buffer)
buffer
<Buffer> | <TypedArray> | <DataView>
将原始字节写入序列化器的内部缓冲区。 反序列化器将需要一种计算缓冲区长度的方法。 用于自定义 serializer._writeHostObject()
内部。
serializer._writeHostObject(object)
object
<Object>
调用此方法以写入某种主机对象,即由原生 C++ 绑定创建的对象。 如果无法序列化 object
,则应抛出适当的异常。
此方法不存在于 Serializer
类本身,但可以由子类提供。
serializer._getDataCloneError(message)
message
<string>
调用此方法以生成在无法克隆对象时将抛出的错误对象。
此方法默认为 Error
构造函数,并且可以在子类上被覆盖。
serializer._getSharedArrayBufferId(sharedArrayBuffer)
sharedArrayBuffer
<SharedArrayBuffer>
当序列化器将要序列化 SharedArrayBuffer
对象时,将调用此方法。 它必须返回对象的无符号 32 位整数 ID,如果此 SharedArrayBuffer
已经被序列化,则使用相同的 ID。 反序列化时,此 ID 将传递给 deserializer.transferArrayBuffer()
。
如果无法序列化对象,则应抛出异常。
此方法不存在于 Serializer
类本身,但可以由子类提供。
serializer._setTreatArrayBufferViewsAsHostObjects(flag)
flag
<boolean> 默认:false
指示是否将 TypedArray
和 DataView
对象视为宿主对象,即将其传递给 serializer._writeHostObject()
。
类: v8.Deserializer
新增于: v8.0.0
new Deserializer(buffer)
buffer
<Buffer> | <TypedArray> | <DataView> 由serializer.releaseBuffer()
返回的缓冲区。
创建一个新的 Deserializer
对象。
deserializer.readHeader()
读取并验证标头(包括格式版本)。 例如,可能会拒绝无效或不支持的线格式。 在这种情况下,会抛出一个 Error
。
deserializer.readValue()
从缓冲区反序列化一个 JavaScript 值并返回它。
deserializer.transferArrayBuffer(id, arrayBuffer)
id
<integer> 一个 32 位无符号整数。arrayBuffer
<ArrayBuffer> | <SharedArrayBuffer> 一个ArrayBuffer
实例。
标记一个 ArrayBuffer
,表示它的内容已经带外传输。 将序列化上下文中对应的 ArrayBuffer
传递给 serializer.transferArrayBuffer()
(或者在 SharedArrayBuffer
的情况下,从 serializer._getSharedArrayBufferId()
返回 id
)。
deserializer.getWireFormatVersion()
- 返回: <integer>
读取底层线路格式版本。可能主要用于读取旧线路格式版本的遗留代码。可能不会在 .readHeader()
之前被调用。
deserializer.readUint32()
- 返回: <integer>
读取一个原始的 32 位无符号整数并返回它。 用于自定义的 deserializer._readHostObject()
内部。
deserializer.readUint64()
- 返回: <integer[]>
读取一个原始的 64 位无符号整数并将其作为数组 [hi, lo]
返回,其中包含两个 32 位无符号整数条目。 用于自定义的 deserializer._readHostObject()
内部。
deserializer.readDouble()
- 返回: <number>
读取一个 JS number
值。 用于自定义的 deserializer._readHostObject()
内部。
deserializer.readRawBytes(length)
从反序列化器的内部缓冲区读取原始字节。 length
参数必须对应于传递给 serializer.writeRawBytes()
的缓冲区的长度。 用于自定义的 deserializer._readHostObject()
内部。
deserializer._readHostObject()
此方法被调用以读取某种主机对象,即由原生 C++ 绑定创建的对象。 如果无法反序列化数据,则应抛出适当的异常。
此方法本身不存在于 Deserializer
类上,但可以由子类提供。
类: v8.DefaultSerializer
新增于: v8.0.0
Serializer
的一个子类,它将 TypedArray
(特别是 Buffer
)和 DataView
对象序列化为主机对象,并且仅存储它们引用的底层 ArrayBuffer
的一部分。
类: v8.DefaultDeserializer
新增于: v8.0.0
Deserializer
的一个子类,对应于 DefaultSerializer
写入的格式。
Promise 钩子
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 });
}
// `settled` 事件发生在 promise 收到解析或拒绝值时。这可能会同步发生,例如在使用
// `Promise.resolve()` 处理非 promise 输入时。
function settled(promise) {
console.log('一个 promise 已解析或拒绝', { promise });
}
// `before` 事件在 `then()` 或 `catch()` 处理程序运行或 `await` 恢复执行之前立即运行。
function before(promise) {
console.log('一个 promise 即将调用 then 处理程序', { promise });
}
// `after` 事件在 `then()` 处理程序运行后或从另一个 `await` 恢复后开始 `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,
});
// 要停止钩子,请调用在其创建时返回的函数。
stopWatchingInits();
stopWatchingSettleds();
stopWatchingBefores();
stopWatchingAfters();
stopHookSet();
promiseHooks.onInit(init)
添加于: v17.1.0, v16.14.0
init
<Function> 当创建 Promise 时调用的init
回调函数。- 返回: <Function> 调用以停止钩子。
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)
添加于: v17.1.0, v16.14.0
settled
<Function> 当 Promise 被解决或拒绝时调用的settled
回调函数。- 返回: <Function> 调用以停止钩子。
settled
钩子必须是一个纯函数。提供一个异步函数将会抛出异常,因为它会产生一个无限的微任务循环。
import { promiseHooks } from 'node:v8';
const stop = promiseHooks.onSettled((promise) => {});
const { promiseHooks } = require('node:v8');
const stop = promiseHooks.onSettled((promise) => {});
promiseHooks.onBefore(before)
添加于: v17.1.0, v16.14.0
before
<Function> 在 Promise 继续执行之前调用的before
回调函数。- 返回: <Function> 调用以停止钩子。
before
钩子必须是一个纯函数。提供一个异步函数将会抛出异常,因为它会产生一个无限的微任务循环。
import { promiseHooks } from 'node:v8';
const stop = promiseHooks.onBefore((promise) => {});
const { promiseHooks } = require('node:v8');
const stop = promiseHooks.onBefore((promise) => {});
promiseHooks.onAfter(after)
添加于: v17.1.0, v16.14.0
after
<Function> 在 Promise 延续执行后调用的after
回调函数。- 返回: <Function> 调用以停止钩子。
after
钩子必须是一个纯函数。 提供一个异步函数将会抛出错误,因为它会产生一个无限的微任务循环。
import { promiseHooks } from 'node:v8';
const stop = promiseHooks.onAfter((promise) => {});
const { promiseHooks } = require('node:v8');
const stop = promiseHooks.onAfter((promise) => {});
promiseHooks.createHook(callbacks)
添加于: v17.1.0, v16.14.0
callbacks
<Object> 要注册的 钩子回调函数init
<Function>init
回调函数。before
<Function>before
回调函数。after
<Function>after
回调函数。settled
<Function>settled
回调函数。
返回: <Function> 用于禁用钩子
钩子回调函数必须是纯函数。 提供一个异步函数将会抛出错误,因为它会产生一个无限的微任务循环。
注册在每个 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) {},
});
Hook 回调
Promise 生命周期中的关键事件被分为四个区域:Promise 的创建、在调用 continuation handler 之前/之后或在 await 周围、以及 Promise resolve 或 reject 时。
虽然这些钩子与 async_hooks
的钩子类似,但它们缺少一个 destroy
钩子。其他类型的异步资源通常表示 socket 或文件描述符,它们具有一个不同的“关闭”状态来表达 destroy
生命周期事件,而 Promise 只要代码仍然可以访问它们,就可以保持可用。垃圾回收跟踪用于使 Promise 适应 async_hooks
事件模型,但是这种跟踪非常昂贵,并且它们甚至可能永远不会被垃圾回收。
由于 Promise 是异步资源,其生命周期通过 Promise 钩子机制进行跟踪,因此 init()
、before()
、after()
和 settled()
回调不能是 async 函数,因为它们会创建更多的 Promise,从而产生无限循环。
虽然此 API 用于将 Promise 事件馈送到 async_hooks
中,但两者之间的顺序未定义。 这两个 API 都是多租户的,因此可能会以相对于彼此的任何顺序生成事件。
init(promise, parent)
在构造 Promise 时调用。 这并不意味着会发生相应的 before
/after
事件,而只是存在可能性。 如果创建一个 Promise 却从未获得延续,则会发生这种情况。
before(promise)
promise
<Promise>
在 Promise continuation 执行之前调用。 这可以是 then()
、catch()
或 finally()
处理程序的形式,也可以是 await
恢复。
before
回调将被调用 0 到 N 次。 如果 Promise 永远没有延续,则 before
回调通常会被调用 0 次。 如果从同一个 Promise 进行了许多延续,则 before
回调可能会被多次调用。
after(promise)
promise
<Promise>
在 promise 继续执行后立即调用。 这可能发生在 then()
、catch()
或 finally()
处理程序之后,或者在另一个 await
之后的 await
之前。
settled(promise)
promise
<Promise>
当 promise 接收到解决值或拒绝值时调用。 在 Promise.resolve()
或 Promise.reject()
的情况下,这可能会同步发生。
启动快照 API
添加于: v18.6.0, v16.17.0
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();
// 从目录读取一系列文件并将它们存储到存储中。
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.env
和 process.argv
:
$ 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
<Function> 将在序列化之前调用的回调函数。data
<any> 可选数据,当调用callback
时,它将被传递给callback
。
添加一个回调,该回调将在 Node.js 实例即将被序列化到快照并退出时被调用。这可以用于释放不应或不能被序列化的资源,或者将用户数据转换为更适合序列化的形式。
回调按照添加的顺序运行。
v8.startupSnapshot.addDeserializeCallback(callback[, data])
已加入版本:v18.6.0, v16.17.0
callback
<Function> 在快照反序列化后调用的回调函数。data
<any> 可选数据,当调用callback
时,它将被传递给callback
。
添加一个回调,该回调将在 Node.js 实例从快照反序列化时被调用。 callback
和 data
(如果提供)将被序列化到快照中,它们可以用于重新初始化应用程序的状态,或者重新获取应用程序在从快照重新启动时需要的资源。
回调按照添加的顺序运行。
v8.startupSnapshot.setDeserializeMainFunction(callback[, data])
已加入版本:v18.6.0, v16.17.0
callback
<Function> 在快照反序列化后作为入口点调用的回调函数。data
<any> 可选数据,当调用callback
时,它将被传递给callback
。
这设置了 Node.js 应用程序从快照反序列化时的入口点。 这只能在快照构建脚本中调用一次。 如果调用,反序列化的应用程序不再需要额外的入口点脚本来启动,而只需调用回调以及反序列化的数据(如果提供),否则仍需要为反序列化的应用程序提供入口点脚本。
v8.startupSnapshot.isBuildingSnapshot()
新增于: v18.6.0, v16.17.0
- 返回: <boolean>
如果 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 数据并返回一个对象。对象的具体内容如下所示。
{
"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
}
这是一个例子。
const { GCProfiler } = require('node:v8');
const profiler = new GCProfiler();
profiler.start();
setTimeout(() => {
console.log(profiler.stop());
}, 1000);