Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【bigo】如何排查nuxt的内存泄露问题 & 优化 #21

Open
tobyjwt opened this issue Feb 1, 2021 · 1 comment
Open

【bigo】如何排查nuxt的内存泄露问题 & 优化 #21

tobyjwt opened this issue Feb 1, 2021 · 1 comment

Comments

@tobyjwt
Copy link

tobyjwt commented Feb 1, 2021

背景

stalar电商平台是公司2020年的新业务,目标市场主要是中东五国,主要技术栈为nuxt。一次常规需求上线后,偶然打开了chrome memory面板,打了几个内存快照,发现内存一直在涨,且无论跳转到什么页面,内存都稳定增长;为排除干扰因素,再快照前手动点击了gc,发现内存的增量仅仅下降了一点点,总体还是呈稳定增长趋势。意识到这是一个比较严重的问题,因为商详页面是有推荐商品模块的,也就是说用户的浏览路径在这里是没有尽头的,很有可能已经有用户出现在浏览大量商品后出现页面崩溃或者浏览器闪退的情况了(目前还缺乏页面崩溃监控,所以还不能确定)。

下图的内存快照,第一张是第一次进入商详页,第二张是在商详页中点击推荐商品进入下一张商详页,重复十次(下文比对内存等变化的截图全部采用这种方式)。 两次生成快照前都手动点击了gc,可以看到内存张了12.3MB

image1

原因排查

nuxt框架问题

观察发现任意页面的跳转,都会让内存稳定增长,即使是一些没有什么逻辑的简单页面,也有一定程度上的内存泄漏,所以首先怀疑nuxt框架或者依赖的其它轮子本身存在着内存泄漏的问题,google了一下发现nuxt的某些小版本确实存在内存泄漏问题,比如: nuxt/issue/7855

既然怀疑框架有问题,首先做的就是将nuxt升级到最新版本(其实我们用的nuxt版本已经比较新了,看nuxt的一些issue貌似是一些小版本有跳跃性的内存问题,比较迷惑),观察发现情况仅仅好转了一点,对于一些简单页面,内存已经不怎么增长了,但是重灾区商详页,还是能看到大幅度内存增长。

代码问题

排除掉框架的影响,回到chrome分析内存泄漏的原因,重新打开商详页并打开performance monitor,重复上文的从商详页点击推荐商品操作,发现JS heep sizeDOM Nodes、JSevent listenters这三项都在稳定增长,同样跳转10次,DOM Nodes从3k左右上涨到了11k,下图为跳转10次后的performance monitor面板截图:

image2

同样是商详页,即使不同商品页面元素有差异,DOM Nodes也不可能有如此巨大的差异,event listenters也有稳定增长,所以怀疑是一些DOM的事件监听没有解绑,导致游离节点一直没有释放,再比较下上文打的两张内存快照,发现确实有非常大的detached node增长,印证了这个猜测。

image3

先从全局方法入手。

一个封装的自定义指令,用作上报

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:

image4

代码为:

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 monitorDOM NodesJS event listeners的数量都没有明显上涨了:

优化前

image22

优化后

image21

游离节点的Delta值(两张快照之间的差值)下降到了0!

优化前

image31

优化后

image32

最后看下内存快照的概览,发现内存已经没有上涨了

优化前

image41

优化后

image42

总结

内存泄漏的原因排查,学会使用chrome devtools工具十分重要,可以参考Chrome Tools,排查思路可以往这几个方面去考虑:

  • 全局变量
  • Dom脱离文档流仍被引用
  • 闭包
  • 第三方轮子未销毁以及重复创建
@ghyghoo8
Copy link

ghyghoo8 commented Feb 3, 2021

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants