如何排查nuxt的內存泄露問題 & 優化

file

本文首發於:github.com/bigo-fronte… 歡迎關注、轉載。javascript

背景

stalar電商平臺是bigo 2020年的新業務,目標市場主要是中東五國,主要技術棧爲nuxt。前端

一次常規需求上線後,偶然打開了chrome memory面板,打了幾個內存快照,發現內存一直在漲,且不管跳轉到什麼頁面,內存都穩定增加;爲排除干擾因素,再快照前手動點擊了gc,發現內存的增量僅僅降低了一點點,整體仍是呈穩定增加趨勢。意識到這是一個比較嚴重的問題,由於商詳頁面是有推薦商品模塊的,也就是說用戶的瀏覽路徑在這裏是沒有盡頭的,頗有可能已經有用戶出如今瀏覽大量商品後出現頁面崩潰或者瀏覽器閃退的狀況了(目前還缺少頁面崩潰監控,因此還不能肯定)。java

下圖的內存快照,第一張是第一次進入商詳頁,第二張是在商詳頁中點擊推薦商品進入下一張商詳頁,重複十次(下文比對內存等變化的截圖所有采用這種方式)。 兩次生成快照前都手動點擊了gc,能夠看到內存張了12.3MBnode

緣由排查

nuxt框架問題

觀察發現任意頁面的跳轉,都會讓內存穩定增加,即便是一些沒有什麼邏輯的簡單頁面,也有必定程度上的內存泄漏,因此首先懷疑nuxt框架或者依賴的其它輪子自己存在着內存泄漏的問題,google了一下發現nuxt的某些小版本確實存在內存泄漏問題,好比: nuxt/issue/7855git

既然懷疑框架有問題,首先作的就是將nuxt升級到最新版本(其實咱們用的nuxt版本已經比較新了,看nuxt的一些issue貌似是一些小版本有跳躍性的內存問題,比較迷惑),觀察發現狀況僅僅好轉了一點,對於一些簡單頁面,內存已經不怎麼增加了,可是重災區商詳頁,仍是能看到大幅度內存增加。github

代碼問題

排除掉框架的影響,回到chrome分析內存泄漏的緣由,從新打開商詳頁並打開performance monitor,重複上文的從商詳頁點擊推薦商品操做,發現JS heep sizeDOM Nodes、JSevent listenters這三項都在穩定增加,一樣跳轉10次,DOM Nodes從3k左右上漲到了11k,下圖爲跳轉10次後的performance monitor面板截圖:web

一樣是商詳頁,即便不一樣商品頁面元素有差別,DOM Nodes也不可能有如此巨大的差別,event listenters也有穩定增加,因此懷疑是一些DOM的事件監聽沒有解綁,致使遊離節點一直沒有釋放,再比較下上文打的兩張內存快照,發現確實有很是大的detached node增加,印證了這個猜想。chrome

先從全局方法入手。瀏覽器

一個封裝的自定義指令,用做上報markdown

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 monitorDOM NodesJS event listeners的數量都沒有明顯上漲了:

優化前

優化後

遊離節點的Delta值(兩張快照之間的差值)降低到了0!

優化前

優化後

最後看下內存快照的概覽,發現內存已經沒有上漲了

優化前

優化後

總結

內存泄漏的緣由排查,學會使用chrome devtools工具十分重要,能夠參考Chrome Tools,排查思路能夠往這幾個方面去考慮:

  • 全局變量
  • Dom脫離文檔流仍被引用
  • 閉包
  • 第三方輪子未銷燬以及重複建立

歡迎你們留言討論,祝工做順利、生活愉快!

我是bigo前端,下期見。

相關文章
相關標籤/搜索