分享

使用 Chrome 开发者工具分析内存问题

 汪子熙 2021-10-03

DevTools 显示了按功能划分的内存分配细目。 默认视图是 Heavy (Bottom Up),它在顶部显示分配最多内存的函数。 Fix memory problems

内存泄漏很容易定义。 如果一个站点逐渐使用越来越多的内存,那么您就会出现泄漏。 但是内存膨胀有点难以确定。 什么是“使用过多内存”?

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

这里的关键是使用 RAIL 模型并关注您的用户。 找出哪些设备受用户欢迎,然后在这些设备上测试您的页面。 如果体验始终不佳,则页面可能超出了这些设备的内存容量。

Monitor memory use in realtime with the Chrome Task Manager

Shift + Esc 打开 Task manager:

允许查看 JavaScript memory:

这两列告诉您有关页面如何使用内存的不同信息:

memory 代表本机内存。 DOM 节点存储在本机内存中。 如果这个值在增加,DOM 节点就会被创建。

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

Visualize memory leaks with Timeline recordings

您还可以使用“Timeline”面板作为调查的另一个起点。 “Timeline”面板可帮助您可视化页面随时间的内存使用情况。

  • 在 DevTools 上打开 Timeline 面板。

  • 启用内存复选框。

  • 进行 recording.

提示:使用强制垃圾回收来开始和结束录制是一种很好的做法。 录制时点击垃圾回收按钮(强制垃圾回收按钮)强制垃圾回收。

考虑下面的例子:

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 节点,并将一百万个 x 字符的字符串推送到 x 数组上。 运行此代码会生成一个时间轴记录,如下面的屏幕截图:

首先,解释用户界面。 概述窗格中的 HEAP 图(NET 下方)表示 JS 堆。 概览窗格下方是计数器窗格。 在这里,您可以看到按 JS 堆(与概览窗格中的 HEAP 图相同)、文档、DOM 节点、侦听器和 GPU 内存细分的内存使用情况。 禁用复选框会将其隐藏在图表中。

现在,将代码与屏幕截图进行比较分析。 如果您查看节点计数器(绿色图表),您会发现它与代码完全匹配。 节点数以离散的步骤增加。 您可以假设节点数的每次增加都是对grow() 的调用。 JS 堆图(蓝色图)并不那么简单。 根据最佳实践,第一次下降实际上是强制垃圾收集(通过按下收集垃圾按钮实现)。 随着记录的进行,您可以看到 JS 堆大小激增。 这是自然而然的:JavaScript 代码会在每次按钮点击时创建 DOM 节点,并在创建一百万个字符的字符串时做了大量工作。 这里的关键是 JS 堆结束时比开始时高(这里的“开始”是强制垃圾收集之后的点)。 在现实世界中,如果您看到这种增加 JS 堆大小或节点大小的模式,则可能意味着内存泄漏。

Discover detached DOM tree memory leaks with Heap Snapshots

DOM 节点只有在页面的 DOM 树或 JavaScript 代码中都没有对它的引用时才能被垃圾回收。 当一个节点从 DOM 树中移除但一些 JavaScript 仍然引用它时,就说它是“分离的”。分离的 DOM 节点是内存泄漏的常见原因。 下面介绍如何使用 DevTools 的堆分析器来识别分离的节点。

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

单击代码中引用的按钮会创建一个带有十个 li 子节点的 ul 节点。 这些节点被代码引用,但不存在于 DOM 树中,因此它们是分离的。

堆快照是识别分离节点的一种方式。 顾名思义,堆快照向您展示了在快照时间点内存在页面的 JS 对象和 DOM 节点之间的分布情况。

要创建快照,请打开 DevTools 并转到 Profiles 面板,选择 Take Heap Snapshot 单选按钮,然后按 Take Snapshot 按钮。

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

在类过滤器文本框中键入 detached 以搜索分离的 DOM 树。

突出显示为黄色的节点在 JavaScript 代码中直接引用了它们。 红色突出显示的节点没有直接引用。 它们之所以活着,是因为它们是黄色节点树的一部分。 通常,您希望关注黄色节点。 修复您的代码,使黄色节点的存活时间不会超过它需要的时间,并且您还可以摆脱作为黄色节点树的一部分的红色节点。

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

Identify JS heap memory leaks with Allocation Timelines

var x = [];

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

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

每次按下代码中引用的按钮时,都会将一百万个字符的字符串添加到 x 数组中。

要记录分配时间线,请打开 DevTools,转到 Profiles 面板,选择 Record Allocation Timeline 单选按钮,按 Start 按钮,执行您怀疑导致内存泄漏的操作,然后按停止记录按钮(停止记录 按钮)完成后。

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

那些蓝条代表新的内存分配。 这些新的内存分配是内存泄漏的候选对象。 您可以放大条形以过滤构造器窗格以仅显示在指定时间范围内分配的对象。

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

Investigate memory allocation by function

使用 Record Allocation Profiler 类型查看 JavaScript 函数的内存分配。

(1) 选择记录分配分析器单选按钮。 如果页面上有工作人员,您可以使用“开始”按钮旁边的下拉菜单将其选为分析目标。

(2) 按开始按钮。

(3) 在要调查的页面上执行操作。

(4) 完成所有操作后按停止按钮。

DevTools 显示了按功能划分的内存分配细目。 默认视图是 Heavy (Bottom Up),它在顶部显示分配最多内存的函数。

Spot frequent garbage collections

如果您的页面似乎经常暂停,那么您可能遇到垃圾收集问题。

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

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

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多