内存泄露与事件移除的必要性
1. 问题
在实际项目中"有幸"遇到一次内存泄露的问题,事情起源于实时数据的世界地图
。
该地图展示实时的订单数据,当某城市产生订单时就会在该城市的位置产生动画。实时展示数据其实是每一分钟拉取接口数据然后做动画。
有一天,领导突然给我发了张图:
DOM Nodes
为 137513
, CPU使用率为 98.6%
,领导告诉我页面有内存泄露,要处理一下。
2. 排查问题
先给个链接看下 Chrome 官方的 解决内存问题。里面提到了 dom nodes
: 只有页面的 DOM 树或 JavaScript 代码不再引用 DOM 节点时,DOM 节点才会被作为垃圾进行回收。 如果某个节点已从 DOM 树移除,但某些 JavaScript 仍然引用它,我们称此节点为“已分离”。已分离的 DOM 节点是内存泄漏的常见原因。
第一反应是实时地图页面的问题: 首先这个页面是前一天加上的,第二天就报内存泄漏了。其次原先的百度 Echarts
迁徙地图,动画的自定义性不强,查询资料之后发现 D3.js
可以实现自定义动画,于是使用 这种方式来做动画,还依赖了 snap
和 eve
,在使用中已经发现在切换页面的时候,body下面会自动 append 不定数量的 svg 元素。当时非常 hack 的在切换页面的时候判断是否有多余的 svg 元素,有则remove掉。看来可能是实时地图的锅。
然后开始对 snap
和 eve
进行调试,慢慢的找到了一点点头绪,在引入 snap
和 eve
的时候,snap
和 eve
会执行一些初始化代码,eve
负责自定义事件。定时(200ms)执行一次
动画代码 doAnimation
会产生svg元素作为动画元素,而在切换页面时,doAnimation会因为eve
中的事件,继续执行一会,导致产生若干个 svg 元素。
那想想是不是可以在组件willUnmount
的时候关闭 eve
的事件?果然,有个eve.off()
函数,加上之后从实时地图页面
切换到 B页面
不会产生多余的 svg 元素了。 但是当我再次切回 实时地图页面
时发现动画也不动了。调试之后发现,当使用 eve.off()
之后,原本引入eve
,snap
组件时候的初始化代码 (包含设置eve的事件) 设置的事件,也被移除了。 那怎么在页面切回的时候再次重新引入 snap
和 eve
完成事件的设置呢?
后面我就没研究了snap和eve了,因为很快就发现内存泄露的关键不是这个,算是先留了个坑,以后有机会再填上 。
后来发现,即使我把实时地图页面移除,依然会在点击菜单来回切换各个页面的时候增加 dom nodes
的数量,并且不会减少 (不会减少是关键,后面会提到)。于是按照 chrome 推荐的方法进行 抓取快照
, 分析,发现并不能找出问题...,可能是我的使用方式不对(再吐槽下那 React的性能优化涉及到DevTools Timeline 分析,我也没用成功...),但是就是没帮助我排查出问题。然后我把这情况告诉同事请求帮助了。
我投降,以下是同事的排查过程
回退代码: 把 git 代码回退 -1,测试,回退 -2,测试... 结果发现到很久之前的某一个版本是没有问题的,那么肯定是后续有一些代码导致的。
注释代码: 现在在A,B页面切换会产生
dom nodes
增加,不减少的情况。那么就依次注释A,B页面的代码。在这过程中,发现一个很重要的元素:事件!
,发现注释掉组件关于事件监听的代码,就恢复正常了。
3. 解决
在组件的 ComponentWillUnmount
中移除 addEventListenr
添加的事件,同时把涉及到监听
的代码都在组件移除时销毁。
示例
4. 结果
点开 Performance monitor
,点击 Memory 旁边的 collect garbage
按钮,即可查看到dom nodes数量下降。 collect garbage
是强制进行垃圾回收,即使你不点击该按钮(就默默等待浏览器一会),在浏览器下一次垃圾回收时,你也会看到 dom nodes 下降,点击它只为快速看到效果。
提示:一种比较好的做法是使用强制垃圾回收开始和结束记录。 在记录时点击 Collect garbage 按钮 (强制垃圾回收按钮) 可以强制进行垃圾回收。
以上这句提示还是 chrome 的那篇文章的内容。因此,如果你点击 collect garbage
之后,performance数据没有发生变化(不减少!!!),那么说明依旧有内存泄露问题!
5. 总结
对 Chrome DevTools 的认识还是不够,Timeline,性能的一些调试的还没能结合实际情况理解。
要学会如何排查bug,自己遗留的坑如果出现问题要自己填回去(暂时snap,eve那一块是好的,hack就先hack吧)。
其实切换页面时不光dom nodes数量增加,还有JS event listeners 数量增加,这是不是已经能说明是事件的问题?!
感谢我那些无所不知的同事们。
Last updated