如何排查网页在哪里发生了内存泄漏?

开发 前端
今天我们来学习用 Devtool 的 Performance 和 Memory 工具来找出网页哪里发生了内存泄漏。

大家好,我是前端西瓜哥。

今天我们来学习用 devtool 的 Performance 和 Memory 工具来找出网页哪里发生了内存泄漏。

Performace 面板

首先我们打开浏览器的 devtool,选择 Performance(性能)面板,然后将 Memory 选项勾选上。不勾选的话,就不会记录内存使用情况,内存泄漏分析就无从说起了。

然后进行性能数据收集:

  1. 点击左上角的 “录制” 按钮(一个灰色的圆形),或者点它旁边的 “刷新” 按钮,会重新加载页面并开始记录,这样就不用手动刷新然后手忙脚乱地点录制按钮了;
  2. 在页面上执行可能发生内存泄漏的操作,比如打开一个弹窗,然后再关闭;
  3. 差不多了就再点击 “录制” 按钮,结束录制,然后出现下面图片的结果。

图片

查看内存指标

看看内存的使用情况。有这么几步:

  1. 选中要分析的范围;
  2. 选中 Main(主线程)。只有选中的话,内存图表才能显示主线程对应的信息;
  3. 查看内存图表的指标。

图片

内存图表是一些折线图,记录了内存指标随时间发生的变化。这些内存指标有:JS 堆内存、Document 数、节点数、绑定监听器数量、GPU 内存。

点击它们可显示或隐藏对应的折线图。

对于  JS Heap(11.9MB - 25.6MB) ,它表示的是在当前时间范围内,JS 堆内存最小值为 11.9 MB,最大为 25.6 MB。

将光标悬停在折线图上,可以看到对应的值:

图片

查看内存下限的变化

内存会增长是正常的现象。比如我们调用函数,会创建一些临时变量,导致内存升高。函数执行完,这些变量就没用了,但不会马上回收,而是会在适当的时机进行内存回收,将内存再降下去。

临时分配的短命内存我们并不关心,我们更关注的是一些常驻的内存,对应的要看的是 内存下限的变化。

图片

如果内存下限不断上升,说明常驻内存变大了。大多数情况下是正常的,比如:

  1. 调用函数,将函数返回的结果进行缓存;
  2. 创建新的组件。

也可能是内存泄漏了。

当怀疑是内存泄漏时,我们就可以使用 Memory 面板记录快照,做进一步的排查。

Memory 面板

打开 Memory 面板,点击左上角的 “录制按钮”,生成当前时刻的堆内存快照。然后通过快照了解 JS 对象的内存分布

图片

Summary View

快照结果默认会展示为 概要视图(Summary View)。

图片

这个表格的表格项是基于构造函数进行归类的。可以看到有不少原生的构造函数,还有一堆闭包。

每个项有以下几个属性:

  • Constructor:构造函数。对于没有构造函数的字面量,用类似(string)​ 、(array) 的表示。
  • Distance:到根节点的最短路径。
  • Shallow Size:自己占用的内存大小,不包括它引入的其他对象内存,单位为字节。
  • Retained Size:对象自己以及它引用的对象的内存,单位也是字节。
  • Object Count:对象数量,就是 Constructor 名旁边那个数字。

上面是默认的 Summary View 视图。

除了它,我们还有其他的视图,可以像下面这样进行视图类型的切换。

图片

Comparison View

比较视图(Comparison View)则是用来比较两个快照的变化。

图片

这里我选中了快照 3,然后将对比快照设置为 快照 1。

这个表格表示从快照 1 变成快照 3 发生的变化。没有发生变化的项不会进行展示。

字段有:

  • Constructor:构造函数。
  • #New:新增的对象数量。
  • #Deleted:删除的对象数量。
  • #Delta:总体上的对象变化数量。
  • Alloc.Size:分配的总内存。
  • Freed Size:释放了多少内存。
  • Size Delta:总体上的内存变化。

Containment View

该视图可以让我们从根节点为起点,往下去查看各种对象占用的内存,以及被创建的代码位置等信息。

图片

字段:

  • Object:普通对象或者 DOM 节点:
  • Distance:到根节点的距离。
  • Shallow Size:对象大小,不计算引用的对象。
  • Retained Size:对象大小,但其引用的对象大小也计算在内。

Statistics View

圆环统计表。

各种内存类型的占总内存的百分比情况。

图片

使用 Memory 面板注意事项

尽量减少干扰项的影响力。

  1. 分辨正常的内存变化会的干扰。
  2. 注意开发环境的打包器热加载逻辑等的影响。
  3. 生成环境的代码是混淆过的,一些构造器名字很奇怪,如果可以的话,本地打包一份没经过混淆过的代码做 debug。或者也可以 hover 看看对象结构猜测对应构造器,但效率不高。
  4. 不要有浏览器插件,它们也占用和影响内存,可以用无痕浏览器。

常见内存泄漏原因和排查

忘记及时取消监听器绑定

新手老鸟都容易犯的错误,就是 忘记及时取消监听器绑定。它会导致:

  1. 监听器函数中的对象迟迟不能释放,比如非常大的组件实例。
  2. 绑定大量无用的监听器函数。

怎么排查?

如果监听器是绑定到 DOM 中,我们可以不断执行可以看 Listener 数量的变化。

我写了个弹窗组件,它会在挂载时给 document.body 注册一个函数,然后这个函数会用到这个组件下的变量。但销毁时不取消注册。

打开 Performance 面板,录制,然后不停打开和关闭弹窗,然后结束录制。我们就能看这个 Listeners 的数量的变化,不断地变高那就是忘了。

图片

也可以看看 Memoery 面板中 Comparison View 的快照对比中,EventListener 数量的变化:

图片

具体是哪个,可以看 EventListener 下的最后几个对象。

图片

点击这个蓝色的链接,就能跳到对应的代码位置:

图片

此外,还可以用 Chrome 控制台提供的 getEventListeners(element) 方法,它会返回一个元素事件绑定的函数有哪些。这个方法不是标准方法,是 Chrome 自带的工具方法,只能在控制台上用。我们可以写个方法,从根节点往下找,找出绑定函数数量最多的节点,这个节点多得离谱那就大概率是忘了解绑。

如果不是 DOM 上的监听器,比如发布订阅库的事件集合,那就要看构造器对应对象数量的变化了。

闭包

闭包就是拿到函数 A 内的另一个函数 B,函数 B 会捕获到函数 A 作用域中的变量。

这个就导致了对一些对象的隐式引用,比如一个 DOM 元素。我们需要在不需要使用时将其设置为 null。

我们可以看看有没有什么 Detached 的元素。Detached 表示不在当前文档树上,如果持续增多,可能发生了内存泄漏。

图片

说真的闭包是一个正常的特性,没理由和内存泄漏有关才是。

函数 B 被持有不销毁,自然它捕获的函数 A 中的变量就不能销毁,和对象里有一些属性,这些属性不能销毁没啥区别。函数 B 销毁了,对应的变量自然也就回收了。

有空我再研究下写篇专题。

console

“你到底都打印了些什么啊?”

还有个比较常见的就是,在开发的时候用 console 打印一些对象,合并到主分支又忘记去掉。这些对象是不会被回收的,因为开发者可能会去控制台看看这些对象的内容。这在打印大量大对象时会出性能问题。

排查方法很简单,去看 DevTool 的控制台输出了什么内容,看看有没有大对象。

一些有助于 debug 的 console 是有必要的,但不要滥用。

集合类型的缓存爆炸

我们经常用对象、数组、Map、Set 等集合类型,去做数据的缓存。

当缓存大量对象时,会占用大量的内存,但其中有不少内容是不需要用的。对于前端来说,内存不像后端那样纯金寸土,动不动就是大批量数据要处理,缓存使用起来挺随意的。

对于缓存问题,还要要有点意识,我们可以:

  1. 使用 LRU 算法,将最久没使用的缓存移除,控制缓存数量;
  2. 设置缓存过期时间;
  3. 对于临时缓存,考虑使用  WeakMap 和 WeakSet,它们会在 GC 时强制回收;

这些就没啥好分析的,就看看内存下限变化,某些对象是否变大变多了。

结尾

今天带大家简单入门了 devtool 提供的内存分析工具,但光说不练假把式,还是要多多实战。

责任编辑:姜华 来源: 前端西瓜哥
相关推荐

2023-12-18 10:45:23

内存泄漏计算机服务器

2021-01-18 08:23:23

内存时底层CPU

2019-09-16 17:16:29

Hadoop数据湖数据结构

2021-08-05 15:28:22

JS内存泄漏

2021-08-09 09:54:37

内存泄漏JS 阿里云

2019-12-17 10:01:40

开发技能代码

2020-11-02 09:48:35

C++泄漏代码

2019-11-12 14:41:41

Redis程序员Linux

2013-12-17 15:46:04

iOS开发iOS 内存泄漏

2020-08-17 12:47:07

Mozilla裁员浏览器

2021-01-18 14:04:49

java监控操作

2022-02-08 17:17:27

内存泄漏排查

2011-08-15 10:16:55

内存泄露

2022-05-26 09:51:50

JavaScrip内存泄漏

2017-01-05 19:34:06

漏洞nodejs代码

2024-01-30 10:12:00

Java内存泄漏

2020-08-27 21:36:50

JVM内存泄漏

2019-08-26 09:35:25

命令ping抓包

2010-02-07 09:00:29

AndroidLinux Kerne

2022-06-15 16:04:13

Java编程语言
点赞
收藏

51CTO技术栈公众号