当前位置: 首页 > 科技观察

网页哪里出现内存泄漏怎么排查

时间:2023-03-20 00:44:20 科技观察

大家好,我是前端西瓜哥。今天我们将学习使用devtool的Performance和Memory工具来找出网页上发生内存泄漏的位置。Performance面板首先,我们打开浏览器的devtool,选择Performance(性能)面板,然后勾选Memory选项。如果不勾选,就不会记录内存使用情况,内存泄漏分析就无从谈起。然后收集性能数据:点击左上角的“记录”按钮(灰色圆圈),或者点击旁边的“刷新”按钮,会重新加载页面并开始记录,这样就不用手动了刷新并快速单击录制按钮。在页面上执行可能导致内存泄漏的操作,例如打开弹出窗口然后关闭它。当你快完成时,点击“录制”按钮结束录制,然后会出现下图的结果。查看内存指示器以查看内存使用情况。有几个步骤:选择要分析的范围。检查Main(主线程)。只有选中,内存图才能显示主线程对应的信息。查看内存图的指标。内存图表是记录内存指标随时间变化的折线图。这些内存指标包括:JS堆内存、文档数、节点数、绑定监听器数、GPU内存。单击它们可显示或隐藏相应的折线图。对于JSHeap(11.9MB-25.6MB),表示在当前时间范围内,JS堆内存的最小值为11.9MB,最大值为25.6MB。将光标悬停在折线图上,可以看到对应的值:查看内存下限的变化,内存会增加是正常的。比如我们调用一个函数的时候,会创建一些临时变量,导致内存增加。函数执行后,这些变量就没有用了,但不会马上回收,而是在适当的时候回收,以减少内存。我们不关心临时分配的短命内存,我们更关注一些常驻内存,相应的变化取决于内存的下限。如果内存下限继续上升,说明常驻内存变大了。大多数情况下是正常的,比如:调用一个函数,缓存函数返回的结果。创建新组件。也可能是内存泄漏。当怀疑内存泄漏时,我们可以使用Memory面板记录快照以供进一步调查。内存面板打开内存面板,点击左上角的“记录按钮”,可以生成当前时刻堆内存的快照。然后通过快照了解JS对象的内存分布。默认情况下,摘要视图快照结果将显示为摘要视图。该表的表项是根据构造函数分组的。你可以看到有很多原生构造函数和一堆闭包。每一项都有以下属性:Constructor:构造函数。对于没有构造函数的文字,使用(string)和(array)等表达式。距离:到根节点的最短路径。ShallowSize:自身占用的内存大小,不包括它引入的其他对象内存,单位为字节。RetainedSize:对象本身及其所引用的对象所占的内存,以字节为单位。ObjectCount:对象的数量,也就是Constructor名称旁边的数字。以上是默认的SummaryView视图。除了它,我们还有其他的视图,可以如下切换。ComparisonView比较视图(ComparisonView)用于比较两个快照的变化。这里我选择了snapshot3,然后将对比snapshot设置为snapshot1。这张表代表的是snapshot1到snapshot3的变化,没有变化的项不会显示。这些字段是:构造函数:构造函数。#New:新添加对象的数量。#Deleted:删除的对象数。#Delta:对象总体变化量。Alloc.Size:分配的总内存。释放大小:释放了多少内存。SizeDelta:整体内存变化。ContainmentView这个视图可以让我们从根节点开始,往下查看各个对象占用的内存,以及创建代码的位置等信息。Field:Object:普通对象或DOM节点:Distance:到根节点的距离。ShallowSize:对象大小,不包括引用的对象。RetainedSize:对象的大小,但是它引用的对象的大小也算在内。统计查看甜甜圈统计表。各种内存类型占总内存的百分比。使用Memory面板注意事项来最大程度地减少干扰因素的影响。区分正常的记忆变化。注意开发环境中packager热加载逻辑等的影响,生成环境中的代码被混淆,部分构造函数名字奇怪。可能的话,在本地打包一份没有经过混淆的代码进行调试。或者你可以悬停看对象结构,猜测对应的构造函数,但是效率不高。不要有浏览器插件,它们也占用和影响内存,你可以使用隐身浏览器。内存泄漏的常见原因及排查忘记及时解除绑定监听器新手和老手的一个常见错误是忘记及时解除绑定监听器。会导致:监听函数中的对象长时间无法释放,比如非常大的组件实例。绑定很多无用的监听函数。如何排除故障?如果监听器绑定到DOM,我们可以继续执行,看到监听器数量的变化。我写了一个弹窗组件,挂载的时候会为document.body注册一个函数,然后这个函数会使用这个组件下的变量。但是,注册被销毁时不会被取消。打开性能面板,录制,然后不断打开和关闭弹窗,然后结束录制。我们可以看到Listener数量的变化,如果一直变高,说明忘记了。也可以在Memoery面板的ComparisonView的快照对比中看一下EventListener数量的变化:具体是哪一个,可以看EventListener下的最后几个对象。点击蓝色链接跳转至相应代码位置:另外,您还可以使用Chrome控制台提供的getEventListeners(element)方法,该方法会返回元素事件绑定的函数。该方法不是标准方法,是Chrome自带的工具方法,只能在控制台使用。我们可以写一个方法,从根节点往下查找,找到绑定函数最多的节点。如果节点太多,很有可能我们忘了解绑。如果不是DOM上的监听器,比如发布订阅库的事件集合,则依赖于构造函数对应的对象个数的变化。Closure闭包是在函数A中获取另一个函数B,函数B会捕获函数A范围内的变量。这会导致对某个对象的隐式引用,比如DOM元素。当我们不需要它时,我们需要将它设置为null。我们可以看看是否有任何分离元素。分离意味着它不在当前文档树上。如果继续增加,则可能发生了内存泄漏。说真的,闭包是一个正常的特性,没有理由与内存泄漏有关。函数B是持有不销毁的,那么它捕获的函数A中的变量自然是不能销毁的,这和对象中有一些属性是不能销毁的没什么两样。当函数B被销毁时,相应的变量自然会被回收。有空再研究,再写一个题目。控制台“你到底打印了什么?”还有一个很常见的事情就是你在开发的时候用控制台打印了一些对象,合并到主分支后忘记移除了。这些对象不会被回收,因为开发者可能会去控制台看到这些对象的内容。在打印大量大对象时,这可能会导致性能问题。排查方法很简单,看DevTool控制台的输出,看看有没有大对象。一些有用的调试控制台是必要的,但不要滥用它。集合类型缓存爆炸我们经常使用对象、数组、Map、Set等集合类型来缓存数据。当缓存大量对象时,会占用大量内存,但其中很多是不需要的。对于前端来说,内存没有后端那么纯粹。每次都有大量的数据要处理,缓存用起来相当随意。对于缓存问题,我们需要有一点认识,我们可以:使用LRU算法,去掉最长时间没有被使用的缓存,控制缓存的数量。设置缓存过期时间;对于临时缓存,可以考虑使用WeakMap和WeakSet,在GC时会强制回收。分析这些没什么,就看内存下限的变化,是不是有些对象变大了,变多了。最后,今天给大家简单介绍一下devtool提供的内存分析工具,但是如果不练假动作,还是需要多多实战的。