blog
blog copied to clipboard
【bigo】如何排查nuxt的内存泄露问题 & 优化
背景
stalar电商平台是公司2020年的新业务,目标市场主要是中东五国,主要技术栈为nuxt。一次常规需求上线后,偶然打开了chrome memory面板,打了几个内存快照,发现内存一直在涨,且无论跳转到什么页面,内存都稳定增长;为排除干扰因素,再快照前手动点击了gc,发现内存的增量仅仅下降了一点点,总体还是呈稳定增长趋势。意识到这是一个比较严重的问题,因为商详页面是有推荐商品模块的,也就是说用户的浏览路径在这里是没有尽头的,很有可能已经有用户出现在浏览大量商品后出现页面崩溃或者浏览器闪退的情况了(目前还缺乏页面崩溃监控,所以还不能确定)。
下图的内存快照,第一张是第一次进入商详页,第二张是在商详页中点击推荐商品进入下一张商详页,重复十次(下文比对内存等变化的截图全部采用这种方式)。 两次生成快照前都手动点击了gc,可以看到内存张了12.3MB
原因排查
nuxt框架问题
观察发现任意页面的跳转,都会让内存稳定增长,即使是一些没有什么逻辑的简单页面,也有一定程度上的内存泄漏,所以首先怀疑nuxt框架或者依赖的其它轮子本身存在着内存泄漏的问题,google了一下发现nuxt的某些小版本确实存在内存泄漏问题,比如: nuxt/issue/7855
既然怀疑框架有问题,首先做的就是将nuxt升级到最新版本(其实我们用的nuxt版本已经比较新了,看nuxt的一些issue貌似是一些小版本有跳跃性的内存问题,比较迷惑),观察发现情况仅仅好转了一点,对于一些简单页面,内存已经不怎么增长了,但是重灾区商详页,还是能看到大幅度内存增长。
代码问题
排除掉框架的影响,回到chrome分析内存泄漏的原因,重新打开商详页并打开performance monitor
,重复上文的从商详页点击推荐商品操作,发现JS heep size
、DOM Nodes、JS
、event listenters
这三项都在稳定增长,同样跳转10次,DOM Nodes
从3k左右上涨到了11k,下图为跳转10次后的performance monitor
面板截图:
同样是商详页,即使不同商品页面元素有差异,DOM Nodes
也不可能有如此巨大的差异,event listenters
也有稳定增长,所以怀疑是一些DOM
的事件监听没有解绑,导致游离节点一直没有释放,再比较下上文打的两张内存快照,发现确实有非常大的detached node
增长,印证了这个猜测。
先从全局方法入手。
一个封装的自定义指令,用作上报
V.directive('report', {
bind(el) {
if (option.onload) {
el.addEventListener('load', option.onload);
}
if (option.onerror) {
el.addEventListener('error', option.onerror);
}
}
});
增加解绑方法后
V.directive('report', {
bind(el) {
if (option.onload) {
el.addEventListener('load', option.onload);
}
if (option.onerror) {
el.addEventListener('error', option.onerror);
}
},
unbind(el) {
if (option.onload) {
el.removeEventListener('load', option.onload);
}
if (option.onerror) {
el.removeEventListener('error', option.onerror);
}
}
});
类似的还有对scroll监听的一些全局封装等等。
全局的方法扫了一遍后,发现情况好转的仍然不多,回到上文中打的两张内存快照,尝试从详情中找到产生内存泄漏的具体方法。
SkuBlock组件中监听了specsSChange:
代码为:
mounted() {
eventBus.$on('specsSChange', (specsS) => {
this.specsS = specsS;
});
}
修改后:
mounted() {
eventBus.$on('specsSChange', (specsS) => {
this.specsS = specsS;
});
},
beforeDestroy() {
eventBus.$off('specsSChange');
}
还有一些类似监听方法,修改方式类同,不一一举例说明。
轮子未销毁
使用一些第三方轮子,需要在组件中创建实例,如果在组件销毁后没有销毁轮子的实例,有可能会导致内存泄漏; 也可以通过内存快照详情,找到具体是哪个组件中的轮子导致了内存泄漏。
例如商详页有一个复制分享链接的功能,使用了clipboard.js
,在商详页中是这样使用的:
mounted() {
const clipboard = new Clipboard('#copyLinkBtn');
clipboard.on('success', () => {
// do something
});
}
我没有去细究clipboard.js
不销毁为什么会引发内存泄漏,但是猜测是引用了DOM对象没有释放的原因,修改方式也很简单,调用轮子提供的销毁方法即可
mounted() {
this.clipboard = new Clipboard('#copyLinkBtn');
this.clipboard.on('success', () => {
// do something
});
},
beforeDestroy() {
if (this.clipboard) {
this.clipboard.destroy();
}
}
最终效果
全部修改上线后,同样还是用商详页点击推荐商品进入下一个商详页的方法,重复十次,来测试内存泄漏情况,首先观察performance monitor
,DOM Nodes
和JS event listeners
的数量都没有明显上涨了:
优化前
优化后
游离节点的Delta
值(两张快照之间的差值)下降到了0!
优化前
优化后
最后看下内存快照的概览,发现内存已经没有上涨了
优化前
优化后
总结
内存泄漏的原因排查,学会使用chrome devtools
工具十分重要,可以参考Chrome Tools,排查思路可以往这几个方面去考虑:
- 全局变量
-
Dom
脱离文档流仍被引用 - 闭包
- 第三方轮子未销毁以及重复创建
👍