修复内存问题

了解如何使用 Microsoft Edge 和 DevTools 查找影响页面性能的内存问题,包括内存泄漏、内存膨胀和频繁的垃圾回收。

  • 了解页面当前在 Microsoft Edge 浏览器任务管理器中使用了多少内存。
  • 使用内存工具可视化一段时间内的 内存 使用情况。
  • 使用堆快照识别) 内存泄漏的常见原因 (分离 DOM 树。
  • 了解何时在 JavaScript 堆中分配新内存, (JS 堆) 时间线上的分配检测

另请参阅 使用分离元素工具调试 DOM 内存泄漏

概述

本着 RAIL 性能模型的精神,性能工作的重点应该是用户。

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

  • 随着时间的推移,页面的性能会逐渐恶化。 这可能是内存泄漏的症状。 内存泄漏是页面中的 bug 导致页面随着时间的推移逐渐使用越来越多的内存时。

  • 页面的性能一直很差。 这可能是内存膨胀的症状。 内存膨胀表示页面使用的内存超过最佳页面速度所需的内存。

  • 页面的性能延迟或似乎经常暂停。 这可能是垃圾回收频繁的症状。 垃圾回收是浏览器回收内存时。 浏览器决定何时发生这种情况。 在集合期间,将暂停运行的所有脚本。 因此,如果浏览器进行大量垃圾回收,脚本运行时将暂停很多。

内存膨胀:“太多”是多少?

内存泄漏易于定义。 如果站点逐渐使用越来越多的内存,则存在泄漏。 但内存膨胀有点难固定。 什么限定为“使用过多内存”?

这里没有硬数字,因为不同的设备和浏览器具有不同的功能。 在高端智能手机上平稳运行的同一页面可能会在低端智能手机上崩溃。

此处的关键是使用 RAIL 模型并专注于用户。 了解哪些设备在用户中很受欢迎,然后在这些设备上测试页面。 如果体验一直不好,页面可能会超过这些设备的内存功能。

使用 Microsoft Edge 浏览器任务管理器实时监视内存使用情况

使用 Microsoft Edge 浏览器任务管理器作为内存问题调查的起点。 Microsoft Edge 浏览器任务管理器是一个实时监视器,可告诉你页面当前使用的内存量。

  1. Shift+Esc 或转到 Microsoft Edge main菜单,然后选择“更多工具>”“浏览器任务管理器”以打开 Microsoft Edge 浏览器任务管理器。

    打开 Microsoft Edge 浏览器任务管理器

  2. 右键单击 Microsoft Edge 浏览器任务管理器的表头,然后启用 JavaScript 内存

    启用 JavaScript 内存

这两列告诉你页面如何使用内存的不同内容:

  • “内存”列表示本机内存。 DOM 节点存储在本机内存中。 如果此值增加,则会创建 DOM 节点。

  • JavaScript 内存列表示 JS 堆。 此列包含两个值。 你感兴趣的值是活动数字 (括号中的数字) 。 实时数字表示页面上可访问的对象使用的内存量。 如果此数字增加,则创建新对象或现有对象正在增加。

使用性能工具可视化内存泄漏

还可以使用 性能 工具作为调查的另一个起点。 性能工具可帮助你直观显示页面随时间推移的内存使用情况。

  1. 在 DevTools 中,打开 “性能 ”工具。

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

每次单击代码中引用的按钮时,都会向文档正文追加 10,000 div 个节点,并将包含 1,000,000 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 liul子级的节点。 节点由代码引用,但它们在 DOM 树中不存在,因此每个节点都是分离的。

堆快照是标识分离节点的一种方法。 顾名思义,堆快照显示快照时间点页面的 JS 对象和 DOM 节点之间的内存分布方式。

创建快照:

  1. 打开 DevTools 并转到 “内存” 工具。

  2. 单击“堆快照”单选按钮,然后单击工具底部的“采取快照”按钮。

    获取堆快照

    处理和加载快照可能需要一些时间。

  3. 快照完成后,从左侧面板中选择它, (名为 HEAP SNAPSHOTS) 。

  4. “类筛选器 ”文本框中,键入 Detached以搜索分离的 DOM 树:

    分离节点的筛选

  5. 展开克拉以调查分离的树:

    调查分离的树

  6. 单击某个节点以进一步调查它。

    在“ 对象 ”窗格中,可以看到有关引用节点的代码的详细信息。 例如,在下图中, detachedTree 变量引用节点。

  7. 若要修复特定的内存泄漏,请研究使用 detachedTree 变量的代码,并确保在不再需要节点时删除对节点的引用。

调查节点

使用时间线上的分配检测识别 JS 堆内存泄漏

时间线上的分配检测是另一个工具,可帮助跟踪 JS 堆中的内存泄漏。

使用以下代码演示时间线的分配检测

var x = [];
function grow() {
    x.push(new Array(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);

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

若要在 时间线 上记录分配检测,请:

  1. 打开 DevTools,然后选择“ 内存 ”工具。

  2. 单击“时间线”上的“分配检测”单选按钮,然后单击“开始”按钮。

  3. 执行你怀疑导致内存泄漏的操作。

  4. 完成后,单击“ 停止录制堆配置文件停止录制 ”按钮。

  5. 录制时,请注意时间线的分配检测上是否显示任何蓝色条形,如下图所示:

    新分配

    这些蓝色条表示新的内存分配。 这些新的内存分配是内存泄漏的候选项。

  6. 放大条形图以筛选 “构造函数 ”窗格,以仅显示在指定时间范围内分配的对象。

    缩放分配时间线

  7. 展开对象并选择值,在“ 对象 ”窗格中查看更多详细信息。

    例如,在下图中,新分配的对象的详细信息指示它已 x 分配给作用域中的 Window 变量:

对象详细信息

按函数调查内存分配

使用 分配采样 分析类型可以查看 JavaScript 函数的内存分配。

记录分配采样

  1. 单击“ 分配采样 ”单选按钮。

  2. 如果页面上有辅助角色,则可以使用 “开始 ”按钮旁边的下拉菜单选择该辅助角色作为分析目标。

  3. 单击“ 开始” 按钮。

  4. 在网页上,执行要调查的操作。

  5. 完成所有操作后,单击“ 停止 ”按钮。

DevTools 按函数显示内存分配的细目。 默认视图为 “重 (自下而上) ”,该视图在顶部显示分配了最多内存的函数。

分配采样

使用分配采样的其他设置减少垃圾

默认情况下, 分配采样 分析类型仅报告在录制会话结束时仍处于活动状态的分配。 在对时间线类型使用分配采样分配检测进行分析时, (GC'd) 创建、删除和垃圾回收的对象不会显示在内存工具中。

可以信任浏览器清理代码中的垃圾。 但是,请务必考虑到 GC 本身是一项昂贵的操作,并且多个 GC 可能会降低用户对网站或应用的体验。 在“ 性能 ”工具中录制并打开“ 内存 ”复选框时,可以看到 GC 操作发生在陡峭的悬崖上, (堆图表中的) 突然减少。

性能工具中显示的 GC 操作

通过减少代码创建的垃圾量,可以降低每个 GC 的成本和 GC 操作的数量。 若要跟踪 GC 丢弃的对象,请使用设置配置 分配采样 分析类型。

  1. 单击“ 分配采样 ”选项按钮。

  2. 单击“ 包括主要 GC 丢弃的对象 ”和 “包括次要 GC 丢弃的对象” 设置。

    分配采样 GC 设置

  3. 单击“ 开始” 按钮。

  4. 在网页上,执行要调查的操作。

  5. 完成所有操作后,单击“ 停止 ”按钮。

DevTools 现在跟踪录制期间 GC 的所有对象。 使用这些设置可了解网站或应用产生的垃圾量。 分配采样报告的数据将帮助你识别产生最多垃圾的函数。

如果要调查仅在特定主要或次要 GC 操作期间为 GC 的对象,请适当配置设置以跟踪你关心的操作。 若要详细了解主要 GC 和次要 GC 之间的差异,请参阅 垃圾箱谈话:Orinoco 垃圾回收器 |V8 JavaScript 引擎开发人员博客

发现频繁的垃圾回收

如果页面似乎经常暂停,则可能是垃圾回收问题。

可以使用 Microsoft Edge 浏览器任务管理器或性能内存记录来发现频繁的垃圾回收。

  • 在 Microsoft Edge 浏览器任务管理器中,频繁上升和下降 的内存JavaScript 内存 值表示频繁的垃圾回收。

  • 在性能记录中,频繁更改 (对 JS 堆或节点计数图) 上升和下降表示垃圾回收频繁。

确定问题后,可以对时间线录制使用分配检测来找出分配内存的位置以及导致分配的函数。

注意

此页面的某些部分是根据 Google 创建和共享的作品所做的修改,并根据 Creative Commons Attribution 4.0 International License 中描述的条款使用。 原始页面 在此处 找到,由 Kayce Basques (Technical Writer、Chrome DevTools & Lighthouse) 创作。

Creative Commons 许可证 此作品根据 Creative Commons 署名 4.0 国际许可获得许可