解决内存问题

了解如何使用 Chrome 和开发者工具查找影响页面性能的内存问题,包括内存泄漏、内存膨胀和频繁的垃圾回收。

摘要

  • 使用 Chrome 任务管理器了解您的网页目前使用了多少内存。
  • 使用时间轴记录直观呈现一段时间内的内存用量。
  • 使用堆快照确定已分离的 DOM 树(内存泄漏的常见原因)。
  • 使用分配时间线记录了解新内存在 JS 堆中的分配时间。

概览

RAIL 性能模型的精髓中,性能工作的重点应该是用户。

内存问题很重要,因为它们通常会被用户察觉。用户可通过以下方式察觉内存问题:

  • 网页的性能会随着时间的推移逐渐变差。这可能是内存泄漏的症状。内存泄漏是指页面中的错误导致页面随着时间推移使用的内存越来越多。
  • 网页的效果一直很糟糕。这可能是内存膨胀的症状。内存膨胀是指页面为实现最佳速度而使用的内存量超出所需的内存量。
  • 网页性能延迟或似乎经常暂停。这可能是频繁垃圾回收的症状。垃圾回收是指浏览器收回内存。浏览器会决定何时发生这种情况。在回收期间,所有脚本执行都会暂停。因此,如果浏览器经常进行垃圾回收,脚本执行就会被频繁暂停。

内存膨胀:如何界定“过多”?

内存泄漏很容易定义。如果网站使用的内存越来越多,则说明发生内存泄漏。但内存膨胀比较难以界定。什么情况才算是“使用过多内存”?

这里不存在硬性数字,因为不同的设备和浏览器具有不同的功能。 在高端智能手机上流畅运行的同一个网页在低端智能手机上可能会崩溃。

这里的关键是使用 RAIL 模型,并以用户为中心。了解哪些设备最受用户欢迎,然后在这些设备上测试您的网页。如果体验一直很糟糕,则网页可能超出这些设备的内存容量。

使用 Chrome 任务管理器实时监控内存使用情况

首先,使用 Chrome 任务管理器调查内存问题。任务管理器是一个实时监控器,可以告诉您页面当前使用的内存量。

  1. 按 Shift+Esc 或转到 Chrome 主菜单,依次选择更多工具 > 任务管理器,以打开任务管理器。

    打开任务管理器

  2. 右键点击任务管理器的表格标题,并启用 JavaScript memory

    启用 JS 内存

这两列可让您从不同方面了解网页使用内存的方式:

  • Memory 列表示原生内存。DOM 节点存储在原生内存中。如果此值增大,系统会创建 DOM 节点。
  • JavaScript Memory 列表示 JS 堆。此列包含两个值。您感兴趣的值是实时数字(括号中的数字)。实时数字表示您页面上的可访问对象目前使用的内存量。如果此数字不断增大,要么是正在创建新对象,要么是现有对象在增长。

通过性能记录直观呈现内存泄漏

您还可以使用“性能”面板作为调查的另一个起点。“性能”面板可帮助您直观地查看页面在一段时间内的内存使用情况。

  1. 打开开发者工具上的 Performance 面板。
  2. 选中内存复选框。
  3. 录制视频

为了演示性能内存记录,请考虑使用下面的代码:

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

每次按下代码中引用的按钮时,系统都会将一万个 div 节点附加到文档正文,并将一个包含 100 万个 x 字符的字符串推送到 x 数组。运行此代码会生成类似于以下屏幕截图的时间轴记录:

简单增长示例

首先是界面说明。Overview 窗格(NET 下方)中的 HEAP 图表示 JS 堆。概览窗格下方是计数器窗格。您可以在此处查看按 JS 堆(与 Overview 窗格中的 HEAP 图表相同)、文档、DOM 节点、监听器和 GPU 内存细分的内存用量。停用某个复选框会在图表中隐藏该复选框。

现在,我们根据屏幕截图来分析代码。如果您查看节点计数器(绿色图表),会发现它与代码完全匹配。节点数量以离散步长的方式增加。您可以假设节点数的每次增加都是对 grow() 的调用。JS 堆图(蓝色图)不那么简单明了。根据最佳做法,第一次低谷实际上是强制垃圾回收(通过按 collect garbage 按钮实现)。随着录制的进行,您可以看到 JS 堆大小出现高峰。这是很自然的,也是意料之中的:JavaScript 代码会在每次点击按钮时创建 DOM 节点,在创建由 100 万个字符组成的字符串时,它会执行大量工作。这里的关键在于,JS 堆的结束时间比开始时高(这里的“开头”是强制垃圾回收之后的时间点)。在现实中,如果您看到这种 JS 堆大小或节点大小不断增加的模式,则可能意味着内存泄漏。

使用堆快照发现已分离的 DOM 树内存泄漏

只有当页面的 DOM 树或 JavaScript 代码都没有引用 DOM 节点时,系统才会对 DOM 节点进行垃圾回收。如果某个节点已从 DOM 树中移除,但某些 JavaScript 仍会引用该节点,则该节点称为“已分离”。已分离的 DOM 节点是内存泄漏的常见原因。本部分将介绍如何使用开发者工具的堆性能分析器来识别已分离的节点。

下面是一个已分离 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 节点之间的分布情况。

要创建快照,请打开开发者工具并转到 Memory 面板,选择 Heap Snapshot 单选按钮,然后按 Take Snapshot 按钮。

截取堆快照

系统可能需要一些时间来处理和加载快照。完成后,从左侧面板中将其选中(名为 HEAP SNAPSHOTS)。

Class filter 文本框中输入 Detached 以搜索已分离的 DOM 树。

过滤已分离的节点

展开三角符号以调查分离的树。

调查分离的树

以黄色突出显示的节点具有 JavaScript 代码中对它们的直接引用。以红色突出显示的节点没有直接引用。它们只是处于活跃状态,因为它们是黄色节点树的一部分。一般来说,您需要关注黄色节点。请修复代码,使黄色节点的活跃时间不长于所需的时长,您也需要移除属于黄色节点树的红色节点。

点击黄色节点进行进一步调查。在对象窗格中,您可以看到有关引用它的代码的更多信息。例如,在下面的屏幕截图中,您可以看到 detachedTree 变量正在引用该节点。为了解决这种特定的内存泄漏问题,您需要研究使用 detachedTree 的代码,并确保在不再需要时移除对节点的引用。

调查黄色节点

使用分配时间轴识别 JS 堆内存泄漏

分配时间轴是另一个可以帮助您跟踪 JS 堆中的内存泄漏的工具。

为了显示分配时间轴,请考虑使用以下代码:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

每次按下代码中引用的按钮时,系统都会向 x 数组添加一个包含 100 万个字符的字符串。

如需记录分配时间轴,请打开开发者工具,前往 Profiles 面板,选择 Record Allocation Timeline 单选按钮,按 Start 按钮,执行您怀疑导致内存泄漏的操作,然后在完成时按 stop record 按钮 (“停止录制”按钮)。

记录时,请注意分配时间轴上是否显示任何蓝条,如下面的屏幕截图所示。

新分配

这些蓝色竖线表示新的内存分配。这些新的内存分配可能出现内存泄漏。您可以缩放条形以过滤 Constructor 窗格,以仅显示在指定时间范围内分配的对象。

放大的分配时间轴

展开该对象并点击它的值,在对象窗格中查看其更多详细信息。例如,在下面的屏幕截图中,通过查看新分配对象的详细信息,您可以看到该对象已分配给 Window 范围内的 x 变量。

对象详情

按函数调查内存分配情况

您可以使用 Memory 面板中的 Allocation Sampling 类型来查看 JavaScript 函数的内存分配。

记录分配分析器

  1. 选中 Allocation Sampling 单选按钮。如果页面上有工作器,您可以使用 Start 按钮旁边的下拉菜单将其选为分析目标。
  2. Start 按钮。
  3. 在您想调查的网页上执行操作。
  4. 完成所有操作后,按停止按钮。

开发者工具会按函数显示内存分配明细。默认视图为 Heavy (Bottom Up),它在顶部显示分配了最多内存的函数。

分配配置文件

发现频繁的垃圾回收

如果您的网页经常暂停,则可能存在垃圾回收问题。

您可以使用 Chrome 任务管理器或时间轴内存记录来发现频繁的垃圾回收。在任务管理器中,MemoryJavaScript Memory 值频繁上升和下降表示垃圾回收频繁。在时间轴记录中,JS 堆或节点计数图表频繁上升和下降表示存在频繁的垃圾回收。

确定问题后,您可以使用分配时间轴记录来找出内存分配的位置以及导致分配的函数。