了解如何衡量网页在生产环境中的内存用量,以检测回归问题。
浏览器会自动管理网页内存。每当有网页 创建对象后,浏览器会“在后台”分配一块内存更改为 存储对象。由于内存是有限的资源,因此浏览器会执行 进行垃圾回收,以检测何时不再需要某个对象并释放 底层内存块
不过,检测并不完美, 经证实能够完美的检测 是一项不可能完成的任务因此,浏览器将“对象”与 需要”“可触及对象”这一概念如果网页无法 通过某个对象的变量和其他可到达对象的字段访问该对象; 那么浏览器就能够安全地收回对象这两种模式之间的区别 两种概念都会导致内存泄漏,如以下示例所示。
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
此处不再需要更大的数组 b
,但浏览器不需要
收回,因为它仍可通过回调中的 object.b
访问。因此
较大数组的内存泄漏。
内存泄漏在网页上很常见。 引入事件监听器非常简单,只需忘记取消注册事件监听器即可, 意外从 iframe 捕获对象,即没有关闭 Worker, 累积数组中的对象,依此类推。如果网页存在内存泄漏 那么其内存使用量会随着时间的推移而增加 使用户感到臃肿。
解决此问题的第一步是对其进行测量。新的
performance.measureUserAgentSpecificMemory()
API 让开发者可以
衡量其网页在生产环境中的内存用量,从而检测内存用量
通过本地测试遗漏的泄漏。
performance.measureUserAgentSpecificMemory()
与旧版 performance.memory
API 有何不同?
如果您熟悉现有的非标准 performance.memory
API,
您可能会好奇新 API 与其有何不同它们的主要区别在于
旧 API 会返回 JavaScript 堆的大小,而新 API
用于估算网页使用的内存这种差异会变为
在 Chrome 与多个网页(或
同一网页的多个实例)。在这种情况下,
API 可能会被任意关闭。由于旧版 API 是在
实现专用术语(例如“堆”),那么对它进行标准化将是无望的。
另一个区别是,新的 API 会在 垃圾回收。这样可以减少结果中的噪声,但可能需要 等待结果生成。请注意,其他浏览器可能会决定 实现新 API,而不依赖于垃圾回收。
建议的用例
网页的内存用量取决于事件、用户操作和 垃圾回收。正因如此,Memory Measurement API 适用于 汇总生产环境中的内存使用情况数据。单个调用的结果 没什么用处。应用场景示例:
- 发布新版本网页期间的回归检测,以捕获新的内存泄漏。
- 对新功能进行 A/B 测试,以评估其内存影响并检测内存泄漏。
- 将内存用量与会话时长相关联,以验证是否存在内存泄漏。
- 将内存用量与用户指标相关联,以了解内存用量的总体影响。
浏览器兼容性
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
目前,只有基于 Chromium 的浏览器(从 Chrome 89 开始)才支持该 API。通过 该 API 的结果与实现高度相关,因为浏览器 在内存中表示对象的不同方式 来估算内存用量浏览器可能会将某些内存区域 因此,结果 无法跨浏览器进行比较。只有在比较 同一浏览器的搜索结果。
使用 performance.measureUserAgentSpecificMemory()
功能检测
performance.measureUserAgentSpecificMemory
函数将不可用或可能不可用
如果执行环境不满足,则失败并显示 SecurityError
旨在防范跨源信息泄露的安全要求。
它依赖于跨域隔离,网页可以激活该隔离功能
方法是设置 COOP+COEP 标头。
可在运行时检测到支持情况:
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
} else {
throw error;
}
}
console.log(result);
}
本地测试
Chrome 会在垃圾回收期间执行内存测量 API 不会立即解析结果 promise,而是会等待 进行下一次垃圾回收
在超时一段时间后,调用该 API 会强制执行垃圾回收,即
当前设置为 20 秒,但可能会更早通过
--enable-blink-features='ForceEagerMeasureMemory'
命令行标志缩短了
超时值为零,并且对本地调试和测试很有用。
示例
使用此 API 的建议用途是定义一个全局内存监视器,
对整个网页的内存使用情况进行采样,并将结果发送到服务器
进行汇总和分析。最简单的方法是定期采样,
每 M
分钟训练一次示例。不过,这会给数据带来偏见,因为
样本之间可能会出现内存峰值。
以下示例展示了如何 使用泊松过程进行无偏见内存测量, 可以保证样本在任何时间点出现的几率相同, (演示、来源)。
首先,定义一个函数,该函数使用
随机间隔的 setTimeout()
。
function scheduleMeasurement() {
// Check measurement API is available.
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
console.log('See https://web.dev/coop-coep/ to learn more')
return;
}
if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
return;
}
const interval = measurementInterval();
console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
setTimeout(performMeasurement, interval);
}
measurementInterval()
函数计算随机间隔(以毫秒为单位)
以便平均每五分钟测量一次。请参阅指数
分布。
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
最后,异步 performMeasurement()
函数调用 API,记录
结果,并安排下一次衡量。
async function performMeasurement() {
// 1. Invoke performance.measureUserAgentSpecificMemory().
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
return;
}
// Rethrow other errors.
throw error;
}
// 2. Record the result.
console.log('Memory usage:', result);
// 3. Schedule the next measurement.
scheduleMeasurement();
}
最后,开始衡量。
// Start measurements.
scheduleMeasurement();
结果可能如下所示:
// Console output:
{
bytes: 60_100_000,
breakdown: [
{
bytes: 40_000_000,
attribution: [{
url: 'https://example.com/',
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 20_000_000,
attribution: [{
url: 'https://example.com/iframe',
container: {
id: 'iframe-id-attribute',
src: '/iframe',
},
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 100_000,
attribution: [],
types: ['DOM']
},
],
}
总内存用量估算值会在 bytes
字段中返回。该值为
高度依赖实现,无法跨浏览器进行比较。它可能会
甚至可以在同一浏览器的不同版本之间进行更改。该值包括
内所有 iframe、相关窗口和 Web Worker 的 JavaScript 和 DOM 内存
当前进程
breakdown
列表提供了有关所用内存的更多信息。每个
该条目描述了内存的某个部分,并将其归因于
窗口、iframe 和工作器。types
字段会列出
与内存关联的特定于实现的内存类型。
务必以通用方式处理所有列表,而不是硬编码
基于特定浏览器进行的预测。例如,某些浏览器可能
会返回空的 breakdown
或空的 attribution
。其他浏览器
在 attribution
中返回多个条目,这表明它们无法区分
其中哪个条目拥有该内存
反馈
网页性能社区小组和 Chrome 团队非常愿意
分享您对
performance.measureUserAgentSpecificMemory()
。
向我们介绍 API 设计
API 是否存在无法按预期运行的地方?或者,在那里 还缺少一些属性?提交规范问题 performance.measureUserAgentSpecificMemory() GitHub 代码库中,或添加 您对现有问题的想法。
报告实现存在的问题
您在 Chrome 的实现过程中是否发现了错误?还是
与规范不同?在 new.crbug.com 上提交 bug。请务必
请提供尽可能多的细节,提供重现问题的简单说明
修正 bug,然后将 Components 设置为 Blink>PerformanceAPIs
。
Glitch 非常适用于分享轻松快速的重现问题。
表示支持
您打算使用 performance.measureUserAgentSpecificMemory()
吗?你的公开支持
可帮助 Chrome 团队确定各项功能的优先级,并向其他浏览器供应商展示
但为他们提供支持至关重要向 @ChromiumDev 发送推文
并告诉我们您使用它的地点和方式。
实用链接
致谢
非常感谢 Domenic Denicola、Yoav Weiss 和 Mathias Bynens 对 API 设计进行审核, 以及 Dominik Inführ、Hannes Payer、Kentaro Hara、Michael Lippautz 和 Michael Lippautz 的代码审核 。此外,我们还要感谢 Per Parker、Philipp Weis、Olga Belomestnykh、Matthew Bolohan 和 Neil Mckay 提供了宝贵的用户反馈, 改进了 API。
主打图片,作者:Harrison Broadbent,来源于 Unsplash 用户