JS前端性能優化解決內存泄漏頁面崩潰
發生什麼事瞭?
一個與往常一樣的上午,當我沉浸在業務需求中不可自拔時,突然被拉入到一個事故大群。一臉懵逼的我還以為和之前的每次線上bug一樣僅僅是個小問題時,殊不知是我簡單瞭…
看著群裡的聊天記錄,瞬間一種不好的預感湧上心頭。不會是哪個頁面寫瞭死循環瞭吧?
咋瞭?這是咋瞭?
死去的頁面突然攻擊我?
因為項目本身過於龐大,且用戶反饋不特定頁面崩潰,這使得問題定位難度較大。
經過團隊的討論認為可能是該用戶的組織架構接口數據量過大,當數據到達頁面後需要經過遞歸處理,導致內存不足,且通過調試發現並非初次調用該接口就會導致崩潰,而是需要多次調用才會出現該問題。
而且在調試的過程中還發現因數據量過大,該接口需要長達 30s 的時間才能返回。而這麼長的時間用戶可能已經離開這個頁面瞭。我們嘗試以這個場景進行操作發現,用戶離開頁面並沒有取消已經發出的請求,當該請求響應後會導致內存占用飆升。
因該接口為項目全局接口,經過討論我們決定先修復這個離開頁面不取消請求的問題,測試能否解決崩潰問題。
陷入僵局
當我們在離開頁面時取消未完成的請求後,測試反饋仍然會不定時崩潰,此時的我們有點無從下手瞭。因為我們發現問題貌似有點大瞭。
此時我們已經開始懷疑出現瞭內存泄漏,於是我們祭出瞭 chrome-devtool
使用 Memory
來進行內存占用分析。
經過內存分析發現,內存中存在大量的分離元素未能及時回收。我按照內存快照的指引開始瞭漫長的修改。兩天的修改後發現,雖然修復瞭不少的問題,但是仍然不能有效的降低頁面的內存占用,每當我們跳轉新的頁面時某些頁面仍然不能正常釋放。此時我為瞭高效定位問題,開始使用絕招——刪代碼。
-
將頁面中的組織架構樹刪除——內存占用沒降下來…
-
將頁面的列表刪除——很棒空頁面果真降下來瞭
-
將組織架構樹加上,列表刪除——內存又起來瞭…
此時的我認為組織架構樹的寫法有問題,所以花瞭兩天的時間過瞭一遍組織架構樹的代碼,未發現有什麼異常的地方。
為瞭確認是組織架構樹的問題還是頁面列表的問題,我在項目中新建瞭兩個空白的路由頁面。在這個頁面中單獨使用這兩個組件,都沒有問題。
問題陷入瞭僵局…
垂死病中驚坐起
在我們修復這個問題的期間,另一個用戶也反饋瞭相同的問題過來,不過用戶說他用的是edge瀏覽器。此時同事說edge好像可以撐的更久一點。😮哇,真的嗎?難道和瀏覽器有關系?
於是我又打開瞭edge瀏覽器,點開瞭devtool點開瞭Memory…哎?這是什麼
edge的devtool這麼好嗎,專門有獨立的標簽用來查找分離的元素啊,蠻人性化的嘛。可是這和chrome不一樣呀,咋用呢?(戳這裡)
它真的好智能,竟然已經把所有泄漏的dom按照結構組織好瞭,這樣就不用我再從一堆內存信息中一個個找它們的關聯關系瞭。
從上圖我發現原來是有一個全局的tip()
方法導致瞭整個頁面沒能正常回收,用戶每進一次這個頁面就會在內存裡多一份這個頁面的dom節點。而這個頁面又有完整的組織架構樹,組織架構樹又很大…
查看瞭tip()
方法的源碼發現,這是一個全局的指令,這個指令每次都會創建一個新的tip對象,而這個tip對象與頁面上的dom節點關聯,且永遠不會被銷毀。
類似的問題在列表中有個對表格行拖拽排序的功能,使用瞭第三方包sortablejs
而未調用destory()
方法銷毀sortable
實例,進而導致表格與頁面也未能正確釋放。
勿以善小而不為
到此,大概的原因已經明確瞭。未及時銷毀的無用對象導致瞭大面積的內存泄漏,疊加用戶的配置較低(8G內存,且開啟瞭很多頁面),導致瞭頁面的崩潰。
類似以上的問題在本次修復中還發現不少,如:
- 公共彈窗組件將傳入的子組件持有,在關閉彈窗後未能銷毀子組件。
- 全局的
EventBus
實例未及時調用$off
方法銷毀事件,而事件回調與頁面又產生關聯(如:$refs、$parents
) - 組件接受來自頁面的方法,而該組件未能在頁面離開時銷毀(如在組件內將組件的this綁定在window上),導致整個頁面不能釋放
- 為瞭在頁面resize時能夠調節echarts大小,在window上綁定瞭resize回調,離開頁面時為清除resize事件
- 組件實例化時在document上綁定click事件後,銷毀組件時為清除click事件
以上的每一個問題都是小問題,僅僅是因為沒用在beforeDestroy
中調用Element.removeEventListener()
或者未調用destroy()
方法。但是會導致很大的後患。
修復後的頁面已經能夠達到較為正常的內存占用瞭,在此給大傢放一張測試同學提供的修復前後的內存占用對比圖,相較於修復前的內存占用隻增不減(峰值4G,然後崩潰)到現在的能及時回收(最復雜的頁面峰值500M,普通頁面100M以內),已經有瞭極大的提升。
修改參考
const handleResizeWindow = () => { myChart.resize(); } window.addEventListener("resize", handleResizeWindow); this.$once('hook:beforeDestroy', () => { myChart.dispose() // 銷毀echart實例 window.removeEventListener('resize', handleResizeWindow) })
created () { window.removeEventListener('beforeunload', this.closePage) }, ... beforeDestroy () { window.removeEventListener('beforeunload', this.closePage) }
mounted() { this.$eventBus.$on("eventName", this.handleEvent); this.$once('hook:beforeDestroy', () => { this.$eventBus.$off('eventName', this.handleEvent) }) }
以上就是JS前端性能優化解決內存泄漏頁面崩潰的詳細內容,更多關於JS內存泄漏頁面崩潰的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- vue中的addEventListener和removeEventListener用法說明
- vue關閉瀏覽器退出登錄的實現示例
- VUE生命周期全面系統詳解
- 在vue項目中封裝echarts的步驟
- Qt地圖自適應拉伸的實現示例