前言
彦祖们,内存泄露
应该是前端老生常谈的话题了
但是我们往往停留在理论层面,应付面试官,却十分缺少实战机会
本文是笔者在业务开发中遇到的一次真实案例,以此作为分享
技术栈:vue2
(业务限制,未使用 keep-alive)
先看下 vue
官方文档
场景
业务场景非常简单,就是两个 tab
页面互相切换,但是业务终端不是 pc
而是我们工业互联网
业务中的工控机
因为可执行内存只有 1g
,所以会经常出现卡顿的情况
彦祖们可以想象成那种医院挂号的终端
场景复现
操作流程
彦祖们,看下我此时的界面(主要关注下 DOM节点
)
- 查看
站点组
情况
此时是 846
个
- 点击
单设备界面(单机界面)
发现此时的 DOM节点
增加到了 1587
个
当然这是正常现象
多出来的节点个数不就是我这个 单机界面
渲染的节点个数吗?
- 回到
站点组界面
惊人的发现 DOM 节点
个数飙升到了 2862
- 不断来回切换
在笔者来回切换了五六次之后,此时节点个数来到了 5477
排查问题
猜想
我们初步猜想是 单机界面
存在节点内存泄露,那么该如何来证明这个猜想呢?
DevTools 介绍
我们需要借助 chrome devtools
来排查这个问题
- 打开
devtools
切到内存
tab (英文版自行翻译)
2 . 选取
时间轴上的分配插桩
3 . 点击回收垃圾
(每次录制前需要手动回收,以保证数据准确)
- 在我们的
单机界面
点击开始录制
- 点击结束录制,我们能看到时间桩上会分布
蓝色/灰色
的柱状图
柱状图
的分布展示了新内存分配时机
蓝色部分
表示目前还占用的内存
灰色部分
表示目前已被回收的内存
此案例中我们主要关心的是内存分布区域的构造函数
我们检测下有没有 Vue组件泄露
开始录制时间桩
下面我们来模拟下业务中的场景
也就是站点组
单机界面
来回切换,生成内存分布图
分析时间桩
- 我们利用左右两边的框选工具,定位到第一次切换到
单机界面
的柱状图
此时我已经切到了 站点组
,但是蓝色区域还有大部分没有被回收,让我们来看看是否有单机界面
的内存泄露
- 过滤出
Vue
一个个检查下,是否有哪个组件只存在单机界面
的但是没有被回收
这个 .alter-message
笔者全局搜了一下
并确保只有在 单机界面
使用
前文已经提到,业务中未使用 keep-alive
进行缓存
那么 vue
应该帮我们销毁这个组件了呀
- 查看组件详情
我们来深入看下这个组件的信息,发现 vue
的确已经 destroy
这就很奇怪了,难道是所谓的 游离节点?
不要慌,我们点击组件下面会展示内存分布详情
有些可能无法定位到代码文件,比如这种
这时候笔者就把 26 个泄露的文件一个个检查了一遍...(如果有更好的方式,欢迎评论区留言)
- 查看代码
那么我们就进去看看代码,一探究竟,发现以下三处可疑的地方
一步步定位到这两段代码,彦祖们应该已经发现问题所在了
插入一段八股文
此时不禁想起了这段八股文
面试官:造成内存泄露的情况有哪些?
彦祖: 未正确使用闭包 | 事件监听器未移除 | 定时器忘记清理 | 使用插件时,未销毁 | 意外的使用全局变量未回收 | console.log 未清除
...
回到正题
没错,我们这里就是命中了三个场景
事件监听器未移除
定时器忘记清理
使用 echarts 时,未销毁
导致堆内存不知道你什么时候会销毁这个事件,这个定时器有没有用它也不知道啊~
这样一来也就长期劫持了对 vue组件 的引用
也就是说每次进入单机界面
都需要开辟新的内存以支持组件的渲染
看下这张图
如果一个界面中某个组件未被完全移除
那么整个界面的所有组件所占用的内存都不能被完全释放
验证猜想
其实以上种种都是我们的猜想
接下来我们加上以下三行代码
- 移除全局事件监听
window.removeEventListener('closeException',this.xxx)
- 移除全局定时器
clearInterval(this.timeInterval)
- 销毁 echart 实例
this.myChart.dispose()
再来抓一下内存分布
会发现已经没有泄露的 vue component
了
并且 DOM 节点
会被回收到 870
不会重复上升
改动3行代码
,完美收工,走一个 998 🥳
坑点
彦祖们,补充一个坑点,在调试过程中,最好把 console
全去掉
笔者就在调试过程中遇到了一个坑,发现了这行代码
是的,打印了一下 vue 实例
console.log('实例', this)
结果导致内存怎么都回收不了
给问题定位带来了重大的干扰
不过这也完美解释了为什么 console.log
为什么会导致内存泄露
总结
总结一下,如果彦祖们怀疑 A 页面有内存泄露,那么测试步骤如下
1.打开内存监视
2.A B A B 来回切换
3.关键!!!最后停留在 B 页面(不然 A 页面渲染了就会导致无法回收),停止内存监视
写在最后
本次优化仅仅修改了3行代码
最复杂的问题,往往伴随着最简单的代码改动
但是问题的定位过程,是非常考验彦祖们的能力的
感谢彦祖们的阅读
个人能力有限
如有不对,欢迎指正🌟 如有帮助,建议小心心大拇指三连🌟