手上負責的vue項目最近出現一個這樣的問題,用戶用着用着就出現:」喔唷,崩潰啦!「的提示。javascript
作了如下性能優化嘗試:前端
vue-cropper.js,組件實例不會主動銷燬,須要主動調用destroy方法銷燬。
createjs/easeljs,組件實例須要手動銷燬canvas畫布,maker.stage.canvas = null;maker.stage.removeAllChildren();
vue
createjs/easeljs,maker.stage._eventListeners = null;maker.stage.removeAllEventListeners();
。java
iview的select組件,當數據量過大時,DOM渲染會佔用很大的內存,很是吃性能。所以爲其增長了渲染指定個數的功能,例如首次渲染只渲染20個,以後的搜索從已經加載好的數據中搜索並渲染。git
有必定的收效,可是在仍然存在性能問題,切換菜單的過程當中,Memory中的Javascript VM instance以100MB/次的速度增長,並且仍是在沒有數據的狀況下。github
所以,迫切的須要一次深度的性能優化,以解決當前項目遇到的問題。web
解決完這個問題我將加強技能:chrome
我將記錄如下深度分析內存泄露的相關內容:element-ui
window.performance.memory對象的屬性。canvas
jsHeapSizeLimit: 2197815296 totalJSHeapSize: 12068848 usedJSHeapSize: 10730032
totalJSHeapSize和usedJSHeapSize的區別是什麼?
usedJsHeapSize是內存總數:指的是JS對象佔用的內存,包括V8內部對象。
totalJsHeapSize是當前內存總數:指的是JS堆的佔用的內存,包括任意js對象的空閒內存。
經過如下代碼,能夠觀察當前document的usedJSHeapSize佔用情況,從而分析是否存在內存泄露性能問題。
setInterval(()=>{ console.log(performance.memory); },2000)
經過觀察能夠發現,js佔用內存(不包括空閒內存)在一直升高,停留一段時間之後也GC不到頁面初始化的的大小。
所以能夠得出結論,存在內存泄露。
也能夠在Chrome的任務管理器中,開啓JavaScript使用的內存的監控。可是這樣會開啓看到全部tab甚至是插件的內存佔用信息,不如code的方式直觀和geek。
堆快照。其實就是當前頁面的js對象及其相關的DOM節點的內存分佈狀況。
能夠在內存泄露前生成一份堆快照,再在內存泄露後生成一份堆快照。經過對比的方式,找出兩份堆快照存在的內存泄露點。最好是在一次操做後分析,以便分析出問題。
Shallow Size 是對象自己hold的內存。
js會爲對象自身開闢一些空間用來存儲數據。js中string和array會有明顯的shallow size, 不過它們主要在渲染內存中存儲,在js heap上僅僅暴露一個包裹對象。
渲染內存指的是監測頁面的全部內存:
參考資料:即便是一個小對象,均可能間接的hold了龐大的內存。從而致使自動GC程序不能處理掉這些被間接hold的內存。
這是刪除了對象及其依賴對象後,能夠釋放的內存大小,這些依賴從GC root是沒法訪問到的。
官方解釋很拗口,簡單理解其實就是對象及其依賴對象的內存大小。
# New
新建立的對象個數。
# Deleted
刪除的對象個數。
# Delta
發生變化的所有對象的個數。淨增對象個數。
Alloc.Size
已經分配的使用中的內存空間。
Freed Size
新對象釋放出的內存空間。
Size Delta
發生變化的釋放內存的所有空間。淨增內存空間。
shared [V8的heap/factory.cc]指的是SharedFunctionInfos 這是一個介於函數和已編譯代碼的對象,SFI沒有上下文。
全部的size都是以字節爲單位的。
Note: Both the Shallow and Retained size columns represent data in bytes.
素材列表->產品列表->素材列表,增長了6MB的內存佔用。
通過對比發現,主要增大的是Object的Retained Size,從26913個(37%)增大到32933個(49%),增大了12%。
恰好VueComponent也從377個(10%)增大到600個(22%),也是從增大了12%。
因此初步判定,是因爲VueComponent沒有GC致使的。
第一組疑問(理論):
以上信息是在Summary中展現的,那麼如何對比兩次快照呢?
Chrome DevTools提供了一個很是便利的功能,Comparison,切換到想要對比的Snapshot,便可獲得2次內存佔用的diff。
通過第二次和第一次的對比,咱們獲得這張對比分析圖。
第二組疑問(實踐):
組件做爲實例的組件,不會跟隨父組件自動銷燬嗎?
是否是通用組件的問題?一個通用組件在多處引用,致使頁面銷燬後,當前實例的組件沒有完全銷燬?
#Delta值最高的(closure)是主要的緣由嗎?
在(closure)的末尾,咱們找到很熟悉的通用組件面孔,以此爲出發點去作分析。
./src/components/uploadToOss
組件
shared是很可疑的,點開之後是下圖的場景。
組件在這裏出現,說明這個模塊/組件閉包內部變量使用完後沒有置爲null。
vue並不會監測到組件/模塊再也不使用,因此咱們須要在vue的destroyed或者beforeDestroy生命週期中作主動銷燬。
<script> import ALIOSS from '@/components/uploadToOss'; let commonOSS = new ALIOSS(); export default { beforeDestroy() { commonOSS = null; // 這是新增的代碼,銷燬建立的上傳OSS組件實例,釋放閉包空間 } } </script>
必定要注意,vue是監測不到咱們不用某些模塊的,只有綁定在vue實例上的實例纔會與組件一塊兒銷燬,沒有綁定的必定要主動銷燬。
置爲null前
置爲null後
咱們成功釋放了112byte,也就是0.112Kb的內存!
此次分析給了咱們一個啓示呢?在利用class Filter去搜索constructor,觀察delta size是否爲負數,freed size是否不爲0,這樣就能夠判斷出模塊有沒有完全銷燬。
費了半天勁,最後只優化了0.012Kb,這不和沒優化沒差嗎?
試着從VueComponent的對比找找緣由:在產品列表快照,咱們發現了殘留的未被銷燬的素材列表的Table組件。
因此幾乎能夠肯定的是,切換到素材列表頁面的Table組件,沒有被徹底銷燬,在產品列表中依然能夠找到它的身影。
因此,是iView的Table組件存在內存泄露?仍是vue自己存在內存泄露?
再通過對比element-ui和iView,發現iView確實是存在內存泄漏的,內存佔用一直降不下來,而element-ui過一下子就會降到正常值。因此不是Vue的緣由。
和老大討論了一下,以後可能會替換成其餘的UI框架。
目前的方案是監聽window.performance.memory對象,一段時間內持續大於某個閥值時,會提醒用戶主動刷新頁面,從而釋放出泄露掉的內存。
關於iView內存泄露的討論:
個人驗證方式:
就拿這個來講,我作了以下的切換foo->bar->foo->baz->foo後,獲取到這個快照對比。
從圖上能夠看出,VueComponent新建了612個,刪除了9個,淨增603個,分配了17.296Kb的內存,釋放了0.504Kb的內存(看到這個釋放程度我真的佛了),淨增16.792Kb的內存。形成了16.792Kb的內存泄露。
可能你以爲16.792Kb不算什麼,由於它在個人此次分析裏,內存泄露狀況只排第19,排名第一第二的分別泄露了598Kb,506Kb。
vue中的全局事件銷燬,避免listener內存泄露。
DOM0級事件銷燬
window.onbeforeunload = () => {}; window.onbeforeunload = null; // 銷燬,能夠在vue的destroyed生命週期(最好在這個,由於無需在beforeDestroy引用vue實例)或beforeDestroy。
DOM2級事件銷燬
this.foo= (e) => {} window.addEventListener('resize', this.foo); window.removeEventListener('resize', this.foo);// 銷燬,能夠在vue的beforeDestroy生命週期(引用vue實例最好在這個週期銷燬)或destroyed。
全局事件銷燬前(內存釋放前):
全局事件銷燬後(內存釋放後):
經過觀察能夠發現,一次菜單切換,減小了一個冗餘的全局事件監聽器,性能有些許提高。
通過一系列分析咱們發現,能夠經過如下幾種方式分析內存泄露的問題並修復。
前端同窗在選型前端UI框架時,不妨先測試測試是否存在內存泄露。
斯世濁清,全賴吾輩激揚!
參考資料: