項目是利用vue框架開發的公司內部的異常監控系統,用於顯示java程序運行時的異常信息,包括執行堆棧、代碼、變量等信息顯示。vue
在測試過程當中,部門同事反映:在不一樣的異常信息之間屢次切換,會致使網頁崩潰。在案發現場打開 chrome 的任務管理器,看到這個頁面內存佔用已經達到了9.7G,初步懷疑頁面存在泄漏。java
能夠看到Nodes、Listeners、JS Head(memory) 的階梯式增加,中間的增加節點對應就是每一次操做。很顯然這個操做會引發內存的持續增加,最終發生內存溢出也是瓜熟蒂落了。web
在動手以前,我已知的信息有:chrome
- 變量引用DOM
- 子級DOM不能釋放,會致使父級也不會被釋放
- 若是DOM能被正常GC, 對DOM的事件監聽器也會自動移除
嗯,內容有點多,可是也還算清晰:api
- Distance:到root的引用距離
- Shallow size:對象自己的大小,不包含它引用的數據的大小
- Retained size:對象自身以及全部引用的大小,就是對象總共佔用的內存 (若是它引用的對象不被其餘不可回收的對象引用的時候。用google開發者網站的描述叫:將對象自己連同其沒法從GC根到達的相關對象一塊兒刪除後釋放的內存大小)
該操做的核心代碼大體是這樣的 架構
主要功能是,每次執行setEvent,都將 this.exception 指向新的實例,並交給頁面進行數據展現,而以前被this.exception引用的對象,應該被釋放。
從新收集新的內存快照信息框架
找出差別:將視圖改成差別視圖chrome-devtools
從圖上能夠看到在步驟1以後,出現了不少新增的對象,可是刪除的對象是0。
以 Exception對象爲例,按照步驟1的代碼邏輯,新對象創建,舊對象被釋放,Delta 應該爲零。因此能夠明確知道,這裏是一個問題。不過這裏點開查看變量的引用詳情,並沒獲得太多有用的信息,只知道被哪一個 vue component 引用了,可是component 太多,不太好定位。函數
查看 Listenters, 我看到的畫風基本是這樣的:工具
跟預期的結果一致,都是因爲一些 Nodes 沒有被釋放致使的。不過確實沒有獲得太多方便分析的信息。
另外查看Nodes相關的信息,搜索 Detached, 能夠看到一些 Detatched HTMLDivElement等等相似的對象,也就是在內存中可是沒有在頁面進行渲染的元素
我找了一個detla比較小的、節點功能也清晰(就是用來在頁面中進行代碼高亮的元素)的 Detatched HTMLPreElement 進行分析:
能夠看到實際引用關係爲 div <= div <= div <= vue component <= var-hover <= events <= ... $platform.event...
在這裏 $platform.event 是由平臺 + 模塊的架構設計中,平臺提供的事件 api, 用於全局的事件通訊。
最終將以上引用關係進行翻譯:由平臺提供的事件 $platform.event (全局,綁定的事件函數不會被自動釋放),綁定了一個叫作 var-hover 的事件 => var-hover 的事件函數中引用了一個 vue-component => component 的$el屬性 引用了某個Dom => Dom的父級被子級引用致使不能被GC。
能夠看看 var-hover 的代碼:
var-hover 綁定了一個匿名函數(基本上也能夠知道,沒有給這個事件沒有寫過解綁操做),而後匿名函數中使用了 this, 也就是當前 vue component,這也致使了被這個 component 引用的對象都不能被GC。
因此禍根基本上找到了,接下來要作的就是:修復 -> 從新驗證
這就是$platform.event 的實際實現
var-hover的事件綁定以下
移除了 beforeDestroy 鉤子,業務層看起來也好多了。
這裏的每次峯值,就是剛執行進行操做時進行內存分配的結果,以後每次執行,並無出現內存及 Nodes, Listensers 的累積
再次對比一下修正以前的性能分析結果
可怕的樓梯。。。
多了一個 StackFrameVar 以及一些爲了呈現這個 StackFrameVar 對象多出來的一些EventListener、Observer等,這是因爲兩次呈現的數據自己不同致使的,屬於正常狀況
Exception、 等不少對象的 Delta 已經爲0了(按 Delta倒序排列的)
以上分析圖是寫這篇文章過程當中,回寫部分代碼以後實時分析的,相對而言沒有實際調試時處理得那麼細緻。實際調試過程當中還作了其它操做:
經過以上方式是爲了提供一個徹底純淨可控的分析環境。