欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

手把手教你如何排查Javascript內(nèi)存泄漏

 更新時間:2022年06月18日 09:24:00   作者:粉腸  
本文將通過一些常見的FAQ來帶大家一起學(xué)習(xí)一下怎么用工具定位javascript里的內(nèi)存問題,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

引言

也許你已經(jīng)知道,Chrome DevTools里的Performance面板和Memory面板可以用來定位內(nèi)存問題。但當(dāng)你真正上手使用它們的時候,往往會覺得不知所措 —— 因?yàn)槔锩嬗兄鞣N各樣的選項(xiàng)和功能,讓人眼花繚亂。下面我會通過一些常見的FAQ來帶大家一起學(xué)習(xí)怎么用工具定位javascript里的內(nèi)存問題。

如何判斷我的應(yīng)用發(fā)生了內(nèi)存泄漏

為了證明螃蟹的聽覺在腿上,一個專家捉了只螃蟹并沖它大吼,螃蟹很快就跑了。然后捉回來再沖它吼,螃蟹又跑了。最后專家把螃蟹的腿都切了,又對著螃蟹大吼,螃蟹果然一動不動……

定位內(nèi)存問題的過程其實(shí)也類似,如果你自己都不知道自己的頁面在使用過程中哪些步驟會導(dǎo)致內(nèi)存增長,那很可能就會錯把一個正常的內(nèi)存增長當(dāng)作內(nèi)存泄漏來排查,最后查了半天白忙活。 其實(shí)一個單頁應(yīng)用在使用過程中,內(nèi)存發(fā)生增長是很合理的。例如在開發(fā)過程中,為了優(yōu)化使用體驗(yàn),我們可能會對部分?jǐn)?shù)據(jù)進(jìn)行緩存,這部分緩存的數(shù)據(jù)其實(shí)也會導(dǎo)致內(nèi)存占用的升高,但它是符合預(yù)期的。因此,排查內(nèi)存泄漏的第一步,就是要先梳理一遍自己的代碼,看一下哪部分內(nèi)存的升高是合理的,哪部分內(nèi)存的升高是不合理的。

Performance和Memory都可以用來定位內(nèi)存問題,先用誰呢

答案是先用Performance。 當(dāng)我們懷疑頁面發(fā)生了內(nèi)存泄漏的時候,可以先用Performance錄制一段時間內(nèi)頁面的性能變化。你只需要切換到Performance面板,點(diǎn)擊Record,然后在頁面上正常操作一段時間,最后停止錄制即可。

不斷升高的內(nèi)存下限

如果錄制結(jié)束后,看到內(nèi)存的下限在不斷升高的話,你就要注意了 —— 這里有可能發(fā)生了內(nèi)存泄漏。

除了內(nèi)存增長曲線,Nodes(Dom節(jié)點(diǎn)數(shù)曲線)、Document曲線以及Listener曲線也同樣值得關(guān)注,有時候它們對內(nèi)存問題的定位也很有幫助。

當(dāng)你懷疑發(fā)生了內(nèi)存泄漏的時候,你就可以用Memory面板來進(jìn)一步定位泄漏的源頭了。

通過Memory面板定位內(nèi)存泄漏的流程通常是怎么樣的呢

通常,我們可以從Memory的主界面開始,點(diǎn)擊左上角的圓點(diǎn)就可以記錄下當(dāng)前的堆內(nèi)存快照(heap snapshot)了。

Memory面板

這里推薦一個Gmail團(tuán)隊也在用的 “three snapshot”技巧:

  • 打開DevTools, 切換至Memory面板
  • 先記錄一個堆內(nèi)存快照
  • 在你的頁面上執(zhí)行可能發(fā)生泄漏的操作
  • 再記錄一個堆內(nèi)存快照
  • 重復(fù)執(zhí)行多幾遍步驟3
  • 最后記錄一個堆內(nèi)存快照
  • 選擇最后一個堆內(nèi)存快照,找到頂欄的“All objects”, 切換至”Objects allocated between snapshots 1 and 2”(也可以對2,3執(zhí)行同樣的操作)

過濾出兩份快照之間新分配的對象

8. 切換后,你就能看到兩個快照之間新生成的對象。你可以選擇其中一項(xiàng)點(diǎn)開,看看它的retaining tree里面保留了哪些對象沒有釋放。

Tips:在記錄第一個堆快照之前你可以先做一些“預(yù)熱”操作,避免一些懶加載和緩存策略影響到了對內(nèi)存的分析。

為什么我的內(nèi)存快照記錄下來之后看不懂,還出現(xiàn)了很多奇怪的變量

這也是我排查內(nèi)存泄漏時遇到的第一個問題,為什么教程里的內(nèi)存快照簡潔易懂,我的內(nèi)存快照卻像一本天書?

教程里的內(nèi)存快照

我的內(nèi)存快照

為什么有這么大的差異呢?除去教程里demo代碼比較簡單之外,提前準(zhǔn)備好一個合理的debug環(huán)境也是很重要的。這里我列舉了4點(diǎn)個人覺得對debug內(nèi)存問題很有幫助的措施:

1. 盡量使用沒有混淆的代碼:

打包后的代碼往往經(jīng)過了混淆和壓縮,在生產(chǎn)環(huán)境上這是必要的,但在debug時卻會成為我們的絆腳石,不便于閱讀。

2. 排查問題時使用production模式編譯出來的代碼:

Dev模式下往往會開啟一些方便開發(fā)的特性,例如熱更新等。但它們可能會占用一部分的內(nèi)存,影響到內(nèi)存問題的排查,所以建議還是使用production模式編譯出來的代碼進(jìn)行問題排查。

3. 屏蔽所有瀏覽器插件:

屏蔽瀏覽器插件最快的方式就是打開無痕窗口。瀏覽器插件給我們帶來很多便利,但插件注入的額外邏輯有時也會影響內(nèi)存問題的排查。例如vue-devtools會記錄下每一個vuex mutaions,導(dǎo)致內(nèi)存無法釋放。

4. 在現(xiàn)場打內(nèi)存快照,便于跳轉(zhuǎn)到源代碼所在行:

盡管devTools記錄下來的內(nèi)存快照文件可以單獨(dú)加載展示,但還是建議在記錄下內(nèi)存快照的時候“趁熱”分析,因?yàn)檫@時還能從retaining tree上跳轉(zhuǎn)到代碼所在行,有時候?qū)Χㄎ粏栴}也很有幫助。

跳轉(zhuǎn)到源碼所在行

快照里有一些“Detached DOM tree”,是什么意思

一個DOM節(jié)點(diǎn)只有在沒有被頁面的DOM樹或者Javascript引用時,才會被垃圾回收。當(dāng)一個節(jié)點(diǎn)處于“detached”狀態(tài),表示它已經(jīng)不在DOM樹上了,但Javascript仍舊對它有引用,所以暫時沒有被回收。通常,Detached DOM tree往往會造成內(nèi)存泄漏,我們可以重點(diǎn)分析這部分的數(shù)據(jù)。

Shallow size 和 Retained size,它們有什么不同

Shallow size: 這是對象自身占用內(nèi)存的大小。通常只有數(shù)組和字符串的shallow size比較大。

Retain size: 這是將對象本身連同其無法從 GC 根到達(dá)的相關(guān)對象一起刪除后釋放的內(nèi)存大小。 因此,如果Shallow Size = Retained Size,說明基本沒怎么泄漏。而如果Retained Size > Shallow Size,就需要多加注意了。

Memory里的Summary視圖, Comparison視圖, Dominators視圖和Containment視圖分別有什么不同呢

Summary view

顧名思義,Summary view就是當(dāng)前內(nèi)存快照的一個概覽。我們先介紹一下這個視圖下的每一列是什么意思: - Constructor: 對象的構(gòu)造器。 - Distance:與root的距離。距離越大,處理和加載這個對象的時間就越長。 - Object Count:指定構(gòu)造器創(chuàng)建的對象的數(shù)量。 - Shallow Size:對象自身占用內(nèi)存的大小。 - Retained Size:釋放掉該對象后,能釋放掉的內(nèi)存。

在這個視圖下你可以看到當(dāng)前頁面內(nèi)存的具體構(gòu)成,但如果想定位內(nèi)存問題,下面的Comparison view會更加有用。

Comparison view

Comparison視圖可以讓你對比兩份內(nèi)存快照之間的差異。默認(rèn)是跟上一份快照做對比,當(dāng)然你也可以選擇任意兩份內(nèi)存做對比。這個視圖下每一列的數(shù)據(jù)有點(diǎn)不同: - Constructor: 對象的構(gòu)造器。 - # New: 該對象構(gòu)造器下有多少新對象被創(chuàng)建 - # Deleted: 該對象構(gòu)造器下有多少新對象被銷毀 - # Delta: # New - # Delete的差值 - Alloc.Size:兩份快照之間新分配的內(nèi)存 - Freed Size: 兩份快照之間釋放掉的內(nèi)存 - Size Delta:Alloc Size - Freed Size 的差值

這個視圖絕對是排查內(nèi)存泄漏的利器。當(dāng)你能定位到是哪些操作可能造成內(nèi)存泄漏后,比較操作前后的內(nèi)存快照,很容易就能發(fā)現(xiàn)發(fā)生內(nèi)存泄漏的對象。

Containment view

Containment view提供了一個自下而上的視圖,它允許你瀏覽和探索堆內(nèi)存的內(nèi)容。我們可以用它來分析一些全部變量的引用情況(如window)。

Statistics view

Statistics視圖會用餅圖的形式展示各個類型對象的內(nèi)存占比

Constructor下的(array), Array, (closure), (compiled code)都對應(yīng)的哪些內(nèi)容?

  • (closure): 函數(shù)閉包持有的內(nèi)存引用。
  • (array, string, number, regex): 包含著一系列對象,這些對象的屬性上有對應(yīng)類型變量的引用。
  • (compiled code): Javascript引擎(如V8)為了加快運(yùn)行速度,會對代碼進(jìn)行一次編譯。(compiled code)顧名思義就是指與編譯后的代碼相關(guān)聯(lián)的內(nèi)存。
  • Detached HTMLDivElement等:代碼里對指定類型Dom節(jié)點(diǎn)的引用。

發(fā)現(xiàn)有一個叫feedback_cell的字段經(jīng)常出現(xiàn),它是什么?是它導(dǎo)致了內(nèi)存泄漏嗎?

經(jīng)常出現(xiàn)的feedback_cell

放心,它不會造成內(nèi)存泄漏。它是v8對頻繁運(yùn)行的熱代碼做出的優(yōu)化,會被v8自己回收。詳見這篇文章:Feedback vectors in heap snapshots – Rohit Pagariya

常見的內(nèi)存泄漏場景有哪些?

這里列舉了一些常見的內(nèi)存泄漏場景,遇到內(nèi)存泄漏問題時可以先自查一遍常見場景,個人感覺能解決日常開發(fā)中遇到的90%內(nèi)存泄漏

  • console導(dǎo)致的內(nèi)存泄漏 因?yàn)榇蛴『蟮膶ο笮枰С衷诳刂婆_上查看,所以傳遞給console.log方法的對象是不能被垃圾回收的。我們需要避免在生產(chǎn)環(huán)境用console打印對象。
  • 框架配合第三方庫使用時,沒有及時執(zhí)行銷毀 這點(diǎn)可以參考vue cookbook里的例子
  • 被遺忘的定時器 例如在組件初始化的時候設(shè)置了setInterval,那么在組件銷毀之前記得調(diào)用clearInterval方法取消定時器。
  • 沒有正確移除事件監(jiān)聽器(各種EventBus, dom事件監(jiān)聽等) 這應(yīng)該是最容易犯的一個錯誤,無論新手老手都有可能栽在這里。
    特征:performance里,監(jiān)聽器數(shù)量會持續(xù)上升

持續(xù)上升的監(jiān)聽器數(shù)量

啰嗦一句:盡管大部分同學(xué)都會有主動移除監(jiān)聽器的觀念,但如果姿勢不對,可能依舊會造成內(nèi)存泄漏。下面是一個真實(shí)案例:

// 版本一
mounted() {
    window.addEventListener('resize', debounce(this.handleWidthChange, 100))
},
beforeDestroy() {
    window.removeEventListener('resize', debounce(this.handleWidthChange, 100)) 
}

乍一看好像寫的還不錯,有及時移除監(jiān)聽器,對resize這種頻繁觸發(fā)的事件也加了debounce處理。但其實(shí)這段代碼就導(dǎo)致了內(nèi)存泄漏:每次調(diào)用debounce(this.handleWidthChange, 100)時, 其實(shí)都會返回一個新的函數(shù),導(dǎo)致addEventListener和 removeEventListener方法傳入的回調(diào)函數(shù)已經(jīng)不是同一個回調(diào)函數(shù),監(jiān)聽器沒有被正確移除,內(nèi)存泄漏。

下面來看修改后的代碼:

// 版本二
data() {
    return {
        debounceWidthChange: null
    }
},
mounted() {
    this.debounceWidthChange = debounce(this.handleWidthChange, 100)
    window.addEventListener('resize', this.debounceWidthChange)
},
beforeDestroyed() {
    window.removeEventListener('resize', this.debounceWidthChange)  
}

修改后,監(jiān)聽和移除監(jiān)聽的已經(jīng)是同一個回調(diào)函數(shù)了,看起來似乎已經(jīng)沒問題。然而,這段代碼還是有內(nèi)存泄漏的問題。沒看出問題的小伙伴可以對比一下正確答案:

// 版本三
data() {
    return {
        debounceWidthChange: null
    }
},
mounted() {
    this.debounceWidthChange = debounce(this.handleWidthChange, 100)
    window.addEventListener('resize', this.debounceWidthChange)
},
beforeDestroy() {
    window.removeEventListener('resize', this.debounceWidthChange)  
}

是的,答案非常狗血:Vue只有destroyedbeforeDestroy這兩個生命周期,沒有 beforeDestroyed,所以上面的beforeDestroyed函數(shù)永遠(yuǎn)不會執(zhí)行,導(dǎo)致了內(nèi)存泄漏…

結(jié)語

簡單總結(jié)一下排查內(nèi)存泄漏的常見流程:

1. 用performance面板記錄操作一段時間內(nèi)的內(nèi)存變化,找出可能發(fā)生內(nèi)存泄漏的操作。

2. 用“three snapshot”技巧,記錄下發(fā)生泄漏前后的內(nèi)存快照

3. 用comparison視圖對泄漏前后的內(nèi)存快照進(jìn)行比較,找出泄漏的對象。

4. 重點(diǎn)關(guān)注 Vue Component, Detached HTMLDivElement等Constructor 。

以上就是手把手教你如何排查Javascript內(nèi)存泄漏的詳細(xì)內(nèi)容,更多關(guān)于Javascript內(nèi)存泄漏排查的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • js回調(diào)函數(shù)仿360開機(jī)

    js回調(diào)函數(shù)仿360開機(jī)

    這篇文章主要為大家詳細(xì)介紹了js回調(diào)函數(shù)仿360開機(jī),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • 微信小程序indexOf的替換方法(推薦)

    微信小程序indexOf的替換方法(推薦)

    這篇文章主要介紹了微信小程序indexOf的替換方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-01-01
  • 基于js中的存儲鍵值對以及注意事項(xiàng)介紹

    基于js中的存儲鍵值對以及注意事項(xiàng)介紹

    下面小編就為大家介紹一下基于js中的存儲鍵值對以及注意事項(xiàng)。希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • uniapp中uni-popup的具體使用

    uniapp中uni-popup的具體使用

    本文主要介紹了uniapp中uni-popup的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • JavaScript 七大技巧(一)

    JavaScript 七大技巧(一)

    JavaScript是一門非常流行的編程語言,許多開發(fā)者都會把JavaScript選為入門語言,本文給大家分享javascript七大技巧(一),對javascript技巧相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧
    2015-12-12
  • bootstrap為水平排列的表單和內(nèi)聯(lián)表單設(shè)置可選的圖標(biāo)

    bootstrap為水平排列的表單和內(nèi)聯(lián)表單設(shè)置可選的圖標(biāo)

    為水平排列的表單和內(nèi)聯(lián)表單設(shè)置可選的圖標(biāo)。本文通過示例代碼給大家講解,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2017-02-02
  • javascript循環(huán)鏈表之約瑟夫環(huán)的實(shí)現(xiàn)方法

    javascript循環(huán)鏈表之約瑟夫環(huán)的實(shí)現(xiàn)方法

    這是一道比較經(jīng)典的循環(huán)鏈表問題,在華為上機(jī)筆試中也出現(xiàn)過。 約瑟夫環(huán)是一個數(shù)學(xué)的應(yīng)用問題,下面這篇文章主要就給大家介紹了javascript循環(huán)鏈表之約瑟夫環(huán)的實(shí)現(xiàn)方法,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-01-01
  • 優(yōu)化innerHTML操作(提高代碼執(zhí)行效率)

    優(yōu)化innerHTML操作(提高代碼執(zhí)行效率)

    多數(shù)現(xiàn)代瀏覽器都實(shí)現(xiàn)了innerHTML操作,它的方便性讓我們愛不釋手,但如果使用不當(dāng),很容易出現(xiàn)效率問題,本文通過一個例子來說明如何優(yōu)化innerHTML操作。
    2011-08-08
  • js變量值傳到php過程詳解 將php解析成數(shù)據(jù)

    js變量值傳到php過程詳解 將php解析成數(shù)據(jù)

    這篇文章主要介紹了js變量值傳到php過程詳解 將php解析成數(shù)據(jù),傳參數(shù)去后臺,用ajax,或者原生js方式拼接url。明白原理,洞悉系統(tǒng)是先解析php,再執(zhí)行html代碼和js代碼。,需要的朋友可以參考下
    2019-06-06
  • javascript加號"+"的二義性說明

    javascript加號"+"的二義性說明

    單個的加號作為運(yùn)算符在 JavaScript 中有三種作用。
    2013-03-03

最新評論