了解如何使用 Chrome 和 DevTools 查找影响网页性能的内存问题,包括内存泄漏、内存膨胀和频繁的垃圾回收。
摘要
- 使用 Chrome 任务管理器了解您的网页使用了多少内存。
- 通过时间轴录制功能直观呈现一段时间内的内存用量。
- 使用堆快照找出分离的 DOM 树(内存泄漏的常见原因)。
- 通过分配时间轴记录,了解 JS 堆中何时分配了新内存。
- 确定由 JavaScript 引用保留的已分离元素。
概览
按照 RAIL 性能模型的精神,您在提升性能方面的工作重点应放在用户身上。
内存问题很重要,因为用户通常能感知到这些问题。用户可能会通过以下方式感知内存问题:
- 网页的性能会随着时间的推移而逐渐变差。这可能是内存泄露的症状。内存泄漏是指网页中的 bug 导致网页随着时间的推移而逐渐使用越来越多内存的情况。
- 网页的性能一直很差。这可能是内存膨胀的症状。内存膨胀是指网页使用的内存超出了实现最佳网页速度所需的内存。
- 网页的性能延迟或似乎经常暂停。这可能是频繁进行垃圾回收的症状。垃圾回收是指浏览器回收内存的过程。浏览器会决定何时发生这种情况。在收集期间,所有脚本执行都会暂停。因此,如果浏览器进行大量垃圾回收,脚本执行将会频繁暂停。
内存膨胀:什么是“过多”?
内存泄漏很容易定义。如果某个网站的内存用量逐渐增加,则说明存在内存泄漏。但内存膨胀要难以确定一些。什么情况属于“占用内存过多”?
这里没有具体的数字,因为不同设备和浏览器的功能各不相同。在高端智能手机上顺畅运行的网页在低端智能手机上可能会崩溃。
这里的关键是使用 RAIL 模型并专注于用户。了解用户常用的设备,然后在这些设备上测试您的网页。如果体验一直很差,则表示网页可能超出了这些设备的内存容量。
使用 Chrome 任务管理器实时监控内存用量
使用 Chrome 任务管理器作为内存问题调查的起点。任务管理器是一款实时监控工具,可让您了解网页的内存用量。
按 Shift+Esc 或前往 Chrome 主菜单,然后依次选择更多工具 > 任务管理器以打开任务管理器。
右键点击任务管理器的表格标题,然后启用 JavaScript 内存。
这两个列会分别显示有关网页内存用量方面的不同信息:
- 内存占用列表示操作系统内存。DOM 节点存储在操作系统内存中。如果此值不断增加,则表示正在创建 DOM 节点。
JavaScript 内存列代表 JS 堆。此列包含两个值。您需要关注的是实时号码(括号中的数字)。实时数值表示网页上可访问对象使用的内存量。如果此数值不断增加,则表示系统正在创建新对象,或者现有对象正在增加。
使用性能记录直观呈现内存泄露
您还可以将效果面板作为调查的另一个起点。“性能”面板可帮助您直观地了解网页随时间推移的内存用量。
- 在开发者工具中打开性能面板。
- 选中记忆复选框。
- 录制内容。
为了演示性能内存记录,请考虑以下代码:
var x = [];
function grow() {
for (var i = 0; i < 10000; i++) {
document.body.appendChild(document.createElement('div'));
}
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每次按下代码中引用的按钮时,系统都会将 1 万个 div
节点附加到文档正文,并将一个包含 100 万个 x
字符的字符串推送到 x
数组。运行此代码会生成类似于以下屏幕截图的时间轴记录:
首先,介绍一下界面。概览窗格(位于 NET 下方)中的 HEAP 图表表示 JS 堆。概览窗格下方是计数器窗格。在这里,您可以按 JS 堆(与概览窗格中的 HEAP 图表相同)、文档、DOM 节点、监听器和 GPU 内存查看内存用量明细。停用复选框后,该复选框将从图表中隐藏。
现在,我们来分析一下代码与屏幕截图的对比情况。如果您查看节点计数器(绿色图表),就会发现它与代码完全匹配。节点数会以离散的步骤增加。您可以假定,节点数量每次增加一次都相当于调用一次 grow()
。JS 堆图(蓝色图表)并不那么简单。根据最佳实践,第一个下降实际上是强制垃圾回收(通过按收集垃圾按钮实现)。随着录制过程的进行,您可以看到 JS 堆大小出现峰值。这是正常且预期的结果:JavaScript 代码会在每次点击按钮时创建 DOM 节点,并在创建包含 100 万个字符的字符串时执行大量工作。这里的关键是,JS 堆结束时的位置高于开始时的位置(这里的“开始”是指强制垃圾回收后的点)。在实际应用中,如果您发现 JS 堆大小或节点大小呈现这种增加趋势,则可能意味着存在内存泄漏。
使用堆快照发现分离的 DOM 树内存泄露
只有当页面的 DOM 树或 JavaScript 代码中没有对 DOM 节点的引用时,系统才会对其进行垃圾回收。当节点从 DOM 树中移除,但仍有 JavaScript 引用它时,该节点被称为“分离”节点。分离的 DOM 节点是导致内存泄露的常见原因。本部分介绍了如何使用 DevTools 的堆性能分析器来识别分离的节点。
下面是一个分离 DOM 节点的简单示例。
var detachedTree;
function create() {
var ul = document.createElement('ul');
for (var i = 0; i < 10; i++) {
var li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);
点击代码中引用的按钮会创建一个包含 10 个 li
子项的 ul
节点。这些节点由代码引用,但不存在于 DOM 树中,因此会分离。
堆快照是识别分离节点的一种方法。顾名思义,堆快照会显示在快照时间点内存如何在网页的 JS 对象和 DOM 节点之间分配。
如需创建快照,请打开 DevTools 并前往 Memory 面板,选择 Heap Snapshot 单选按钮,然后按 Take snapshot 按钮。
系统可能需要一些时间来处理和加载该快照。完成后,从左侧面板(名为堆快照)中选择该快照。
在类过滤条件输入框中输入 Detached
,以搜索分离的 DOM 树。
展开箭头以调查分离的树。
点击某个节点可进一步调查。在 Objects 窗格中,您可以详细了解引用该对象的代码。例如,在以下屏幕截图中,您可以看到 detachedTree
变量引用了该节点。如需修复此特定内存泄漏问题,您需要研究使用 detachedTree
的代码,并确保在不再需要时移除对节点的引用。
使用分配时间轴找出 JS 堆内存泄漏
分配时间轴是另一种可帮助您跟踪 JS 堆中的内存泄漏的工具。
如需演示分配时间轴,请考虑以下代码:
var x = [];
function grow() {
x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每次推送代码中引用的按钮时,系统都会向 x
数组添加一个包含 100 万个字符的字符串。
如需记录分配时间轴,请打开 DevTools,前往内存面板,选择时间轴上的分配单选按钮,按
Record 按钮,执行您怀疑会导致内存泄漏的操作,然后在完成后按 Stop recording 按钮。录制时,请注意分配时间轴上是否显示了任何蓝色条,如以下屏幕截图所示。
这些蓝条代表新的内存分配。这些新的内存分配是内存泄漏的候选项。您可以放大某个条状标签,以过滤构造函数窗格,仅显示在指定时间范围内分配的对象。
展开相应对象,然后点击其值,即可在对象窗格中查看有关该对象的更多详细信息。例如,在下面的屏幕截图中,通过查看新分配的对象的详细信息,您可以看到该对象已分配给 Window
作用域中的 x
变量。
按函数调查内存分配
使用内存面板中的分配采样性能分析文件类型,按 JavaScript 函数查看内存分配情况。
- 选择分配抽样单选按钮。如果页面上有 worker,您可以从选择 JavaScript 虚拟机实例窗口中将其选择为性能分析目标。
- 按 Start 按钮。
- 在您要调查的网页上执行操作。
- 完成所有操作后,按 Stop 按钮。
开发者工具会按函数显示内存分配明细。默认视图为占用内存较多(自下而上),该视图会在顶部显示分配内存最多的函数。
识别由 JS 引用保留的对象
已分离的元素配置文件会显示因被 JavaScript 代码引用而保留的已分离元素。
记录分离的元素配置文件,以查看确切的 HTML 节点和节点数。
发现频繁的垃圾回收
如果您的网页似乎经常暂停,则可能存在垃圾回收问题。
您可以使用 Chrome 任务管理器或时间轴内存记录来发现频繁的垃圾回收。在任务管理器中,内存或 JavaScript 内存值频繁上升和下降表示频繁进行垃圾回收。在时间轴记录中,JS 堆或节点数图表频繁上升和下降表示频繁进行垃圾回收。
找出问题后,您可以使用分配时间轴记录来了解内存的分配位置以及哪些函数导致了分配。