背景
内存在浏览器中扮演着一个至关重要的角色,它就是浏览器中的土地,你只有拥有足够的土地,才可建造你的城堡。但是这个土地不是没有限制的。 以 Chrome 为例
- 64位系统
- 物理内存 > 16G : 最大堆内存限制为4G
- 物理内存 <= 16G : 最大堆内存限制为2G
- 32位系统
- 最大堆内存限制为1G
依照目前电脑配置来看,绝大部分用户的内存限制应该为 2G。这个限制只是个理论值,实际值会更小。因为你电脑的内存会被其他应用,或者浏览器的其他页面使用。
为什么要进行内存泄露优化
如果你的网站存在内存泄露,可能会导致浏览器卡顿,甚至崩溃。尤其在一些 SPA 架构的 Web 产品中,用户可能长时间使用产品,且不会去刷新页面。这样随着用户使用时长的增加,泄露的内存会越来越多。最后导致网站越来越卡(频繁触发 GC),最终崩溃。 正好笔者所负责的网站就是一个大型的 SPA 网站。有大量用户每天会使用好几个小时。他们经常会反馈的一个问题就是浏览器奔溃。以至于他们现在都养成了一个习惯,在使用了一段时间以后,他们会习惯性的刷新一下浏览器。这对用户体验来说是一个很大的影响。
如何实现内存泄露优化
要修复内存泄露问题,最重要的是要能够发现内存泄露点。下面我会介绍三种定位内存泄露点的方法。
Chrome Memory Panel
单快照分析
录制一个内存快照,通过 Retained Size 进行排序,逐个分析占用量比较大的堆栈节点。寻找内存泄露点。
这个方法不是特别靠谱,因为很多内存泄露点,你必须要使用很久,它在 Retained Size 中的排序才会比较靠前。而这么做去定位内存泄露问题的效率很低,所以不推荐。
快照对照分析
- 在执行操作之前截取堆快照。
- 执行操作。也就是说,以您认为可能会导致泄露的方式与网页互动。
- 执行反向操作。也就是说,进行相反的互动并重复几次。
- 截取第二个堆快照,将其视图更改为 Comparison 视图,并将其与 Snapshot 1 进行比较。
以上快照录制操作参考 Chrome Devtools 文档 录制完后,对结果进行排序分析,查看那些新增的节点,以及新分配的内存空间。使用此方法可能能找到一些内存泄露点。
Edge Detached Elements tool
这应该是官方提供的最好用的内存泄露定位工具了。可以帮助你快速找到那些无法被 GC 的 Element 节点,也就是 Detached Element。有小伙伴可能会说,Chrome Memory 也可以筛选 Detached Element 来进行查找,但是 Edge 的 Detached Elements tool 有一个好处是,他可以将那些 Elements 以树的方式进行展示。类似 Element Panel,这更符合直觉。
具体使用方式可以参考:使用分离元素工具调试 DOM 内存泄漏
memlab
memlab 是一款内存分析工具,帮助你快速定位网站中的内存泄露。下面我们一起来看一下,memlab 有哪些可以快速定位内存泄露的方法。
前言
memlab 可以帮助你找到内存泄露的堆栈,以及一些节点的变量名,你可以通过变量名相关信息去代码中定位分析,内存泄露的原因。但是,还是更推荐使用通过分析得到的内存堆栈 ID,到 Chrome Memory Panel 中去查找,这样可以得到更多的信息,更有助于找到内存泄露的原因。 下面我们一起看一下 memlab 具体有哪些分析的手段。
Detached Element
功能类似 Edge Detached Elements tool。
图中的 @2173452 节点,即为 Detached Element 节点。
CollectionsHoldingStaleAnalysis
获取那些引用多个对象的 Map,Set,Array 数据。
GlobalVariableAnalysis
对全局变量进行排序,获取那些占用内存比较大的全局变量。
ObjectShallowAnalysis
对 memory 中的 Object 进行浅比较,然后分组。将结构类似的 Object 分到一个组。然后对每个组进行排序。有两个分组逻辑,一个是根据每个组占用的内存大小进行排序,一个是根据每个组中 Object 中的个数进行排序。
ObjectShapeAnalysis
分析方法和 ObjectShallowAnalysis 差不多,简单来说属于 ObjectShallowAnalysis 的加强版,在其基础上增加了 Object 以外的数据分组,比如 Array、Map、Class 等。
ObjectSizeAnalysis
这个分析方案比较简单,只是单纯的对 Object 的 Retained Size 进行排序。
ObjectUnboundGrowthAnalysis
这个分析方案会稍微复杂一点。因为他需要至少两个快照才能进行分析。快照 1 :做了一些操作。快照 2 :做了更多的操作。然后对两张快照中的堆栈进行分析,找到那些内存占用快速增加的节点。
实战 Case
介绍了这么多 memlab,下面我们来一起看一下使用其协助定位到的内存泄露的实战 Case。
Case1
通过 memlab 的 Detached Element 功能定位到未释放的订阅导致的内存泄露。
使用堆栈 @7158601 在 Chrome Memory Panel 进行搜索,找到堆栈节点。
最后发现是由于 Subscribe 的事件,没有正确 Dispose 导致了内存泄露。
Case2
使用 memlab 的 ObjectUnboundGrowthAnalysis 方法分析内存快速增长的一些节点,发现了有一个节点数据增长格外明显。
使用堆栈 @21548079 在 Chrome Memory Panel 进行搜索,找到堆栈节点。
通过对节点中内存占用数据的分析,发现有一个数组内存占用最大。而且每个快照中,也都是该数组中的占用量在变大。
经过分析,发现是由于在使用 formily 时,没有调用 formily 的 onUnmount 方法,导致内存无法释放。