如何處理javascript記憶體洩露
處理方法:1、使用完成之後為其賦值null或重新賦其他值;2、使用現代的垃圾回收演算法;3、保存DOM 元素引用的時候,要小心謹慎;4、透過SessionStack ,回放應用程式中的問題,以免造成記憶體洩漏,也預防增加整個應用程式的記憶體佔用。
本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。 、
記憶體外洩是每個開發者最終都要面對的問題,它是許多問題的根源:反應遲緩,崩潰,高延遲,以及其他應用問題。
什麼是記憶體外洩?
本質上,記憶體外洩可以定義為:應用程式不再需要佔用記憶體的時候,由於某些原因,記憶體沒有被作業系統或可用記憶體池回收。程式語言管理記憶體的方式各不相同。只有開發者最清楚哪些記憶體不需要了,作業系統可以回收。一些程式語言提供了語言特性,可以幫助開發者做這類事情。另一些則寄望於開發者對記憶體是否需要清晰明了。
JavaScript 記憶體管理
JavaScript 是一種垃圾回收語言。垃圾回收語言透過週期性地檢查先前分配的記憶體是否可達,幫助開發者管理記憶體。換言之,垃圾回收語言減輕了「記憶體仍可使用」及「記憶體仍可達」的問題。兩者的差異是微妙而重要的:僅有開發者了解哪些內存在將來仍會使用,而不可達記憶體透過演算法確定和標記,適時被作業系統回收。
JavaScript 記憶體外洩
垃圾回收語言的記憶體外洩主因是不需要的引用。在理解它之前,還需了解垃圾回收語言如何辨別記憶體的可達與不可達。
Mark-and-sweep
大部分垃圾回收語言用的演算法稱為 Mark-and-sweep 。演算法由以下幾個步驟組成:
垃圾回收器建立了一個「roots」清單。 Roots 通常是程式碼中全域變數的引用。 JavaScript 中,「window」 物件是一個全域變量,被當作 root 。 window 物件總是存在,因此垃圾回收器可以檢查它和它的所有子物件是否存在(即不是垃圾);
所有的roots 被檢查和標記為啟動(即不是垃圾)。所有的子物件也被遞歸地檢查。從 root 開始的所有物件如果是可達的,它就不會被當作垃圾。
所有未被標記的記憶體會被當作垃圾,收集器現在可以釋放內存,歸還給作業系統了。
現代的垃圾回收器改良了演算法,但是本質是相同的:可達記憶體被標記,其餘的被當作垃圾回收。
不需要的引用是指開發者明知記憶體引用不再需要,卻由於某些原因,它仍被留在激活的 root 樹中。在 JavaScript 中,不需要的引用是保留在程式碼中的變量,它不再需要,卻指向一塊本該被釋放的記憶體。有些人認為這是開發者的錯誤。
為了理解 JavaScript 中最常見的記憶體洩露,我們需要了解哪種方式的引用容易被遺忘。
三種類型的常見JavaScript 記憶體外洩
1:意外的全域變數
JavaScript 處理未定義變數的方式較為寬鬆:未定義的變數會在全域物件建立一個新變數。在瀏覽器中,全域物件是 window
。
真相是:
函數 foo
內部忘記使用 var
,意外創建了一個全域變數。此例洩漏了一個簡單的字串,無傷大雅,但是有更糟的情況。
另一個意外的全域變數可能由this
建立:
#在JavaScript 檔案頭加上
'use strict'
,可以避免此類錯誤發生。啟用嚴格模式解析 JavaScript ,避免意外的全域變數。
全域變數注意事項
儘管我們討論了一些意外的全域變量,但是仍有一些明確的全域變數產生的垃圾。它們被定義為不可回收(除非定義為空或重新分配)。尤其當全域變數用於暫時儲存和處理大量資訊時,需要多加小心。如果必須使用全域變數儲存大量資料時,請確保用完以後把它設為 null 或重新定義。與全域變數相關的增加記憶體消耗的一個主要原因是快取。快取資料是為了重複使用,快取必須有一個大小上限才有用。高記憶體消耗導致快取突破上限,因為快取內容無法被回收。
2:被遺忘的計時器或回呼函數
在 JavaScript 中使用 setInterval
非常平常。一段常見的程式碼:
此範例說明了什麼:與節點或資料關聯的計時器不再需要,node
物件可以刪除,整個回呼函數也不需要了。可是,計時器回呼函數仍然沒被回收(計時器停止才會被回收)。同時,someResource
如果儲存了大量的數據,也是無法被回收的。
對於觀察者的例子,一旦它們不再需要(或關聯的物件變成不可達),明確地移除它們非常重要。老的 IE 6 是無法處理循環引用的。如今,即使沒有明確移除它們,一旦觀察者物件變成不可達,大部分瀏覽器是可以回收觀察者處理函數的。
觀察者程式碼範例:
物件觀察者與循環引用注意事項
舊版的IE 是無法偵測DOM 節點與JavaScript 程式碼之間的循環引用,會導致記憶體外洩。如今,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收演算法,已經可以正確偵測和處理循環引用了。換言之,回收節點記憶體時,不必非要呼叫 removeEventListener
了。
3:脫離 DOM 的參考
有時,保存 DOM 節點內部資料結構很有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或是陣列很有意義。此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。將來你決定刪除這些行時,需要把兩個引用都清除。
此外還要考慮 DOM 樹內部或子節點的參考問題。假如你的 JavaScript 程式碼中保存了表格某一個 <td>
的引用。將來決定刪除整個表格的時候,直覺認為 GC 會回收除了已保存的 <td>
以外的其它節點。實際情況並非如此:此<td>
是表格的子節點,子元素與父元素是引用關係。由於程式碼保留了 <td>
的引用,導致整個表格仍待在記憶體中。儲存 DOM 元素引用的時候,要小心謹慎。
4:閉包
閉包是 JavaScript 開發的關鍵面向:匿名函數可以存取父級作用域的變數。
程式碼範例:
程式碼片段做了一件事情:每次呼叫replaceThing
,theThing
得到一個包含一個大數組和一個新閉包(someMethod
)的新物件。同時,變數 unused
是一個引用 originalThing
的閉包(先前的 replaceThing
又呼叫了 theThing
)。思緒混亂了嗎?最重要的事情是,閉包的作用域一旦創建,它們有相同的父級作用域,作用域是共享的。 someMethod
可以透過theThing
使用,someMethod
與unused
分享閉包作用域,儘管unused
從未使用,它所引用的originalThing
迫使它保留在記憶體中(防止被回收)。當這段程式碼重複運行,就會看到記憶體佔用不斷上升,垃圾回收器(GC)並無法降低記憶體佔用。本質上,閉包的鍊錶已經創建,每一個閉包作用域攜帶一個指向大數組的間接的引用,造成嚴重的內存洩漏。
Meteor 的部落格文章 說明如何修復此種問題。在
replaceThing
的最後加上originalThing = null
。
Chrome 記憶體剖析工具概覽
Chrome 提供了一套很棒的偵測 JavaScript 記憶體佔用的工具。與記憶體相關的兩個重要的工具:timeline
和 profiles
。
Timeline
timeline 可以偵測程式碼中不需要的記憶體。在此截圖中,我們可以看到潛在的洩漏物件穩定的成長,資料擷取快結束時,記憶體佔用明顯高於擷取初期,Node(節點)的總量也很高。種種跡象表明,程式碼中存在 DOM 節點洩漏的情況。
Profiles
Profiles 是你可以花很多時間關注的工具,它可以保存快照,對比 JavaScript 程式碼記憶體使用的不同快照,也可以記錄時間分配。每一次結果包含不同類型的列表,與內存洩漏相關的有 summary(概要) 列表和 comparison(對照) 列表。
summary(概要) 清單展示了不同類型物件的分配及合計大小:shallow size(特定類型的所有物件的總大小),retained size(shallow size 加上其它與此關聯的對像大小)。它也提供了一個概念,一個物件與關聯的 GC root 的距離。
對比不同的快照的 comparison list 可以發現記憶體外洩。
實例:使用 Chrome 發現記憶體洩露
實質上有兩種類型的洩漏:週期性的記憶體成長導致的洩露,以及偶現的記憶體洩漏。顯而易見,週期性的記憶體外洩很容易發現;偶現的外洩比較棘手,一般容易被忽視,偶爾發生一次可能被認為是最佳化問題,週期性發生的則被認為是必須解決的 bug。
以Chrome 文件中的程式碼為例:
當grow
執行的時候,開始建立p 節點並插入到DOM 中,並且給全域變數一個巨大的數組。透過以上提到的工具可以偵測到記憶體穩定上升。
找出週期性成長的記憶體
timeline 標籤擅長做這些。在 Chrome 中開啟例子,開啟 Dev Tools ,切換到 timeline,勾選 memory 並點擊記錄按鈕,然後點擊頁面上的 The Button
按鈕。過一陣停止記錄看結果:
兩種跡象顯示出現了內存洩露,圖中的 Nodes(綠線)和 JS heap(藍線)。 Nodes 穩定成長,並未下降,這是個顯著的訊號。
JS heap 的記憶體佔用也是穩定成長。由於垃圾收集器的影響,並不那麼容易發現。圖中顯示記憶體佔用忽漲忽跌,實際上每一次下跌之後,JS heap 的大小都比原先大了。換言之,儘管垃圾收集器不斷的收集內存,內存還是週期性的洩漏了。
確定記憶體洩漏之後,我們找找根源所在。
儲存兩個快照
切換到 Chrome Dev Tools 的 profiles 標籤,重新整理頁面,等頁面刷新完成之後,點選 Take Heap Snapshot 儲存快照作為基準。而後再點選 The Button
按鈕,等數秒以後,再儲存第二個快照。
篩選選單選擇 Summary,右側選擇 Objects allocated between Snapshot 1 and Snapshot 2,或是篩選選單選擇 Comparison ,然後可以看到一個對比清單。
此範例很容易找到記憶體洩露,看下 (string)
的 Size Delta
Constructor,8MB,58個新物件。新物件被分配,但是沒有釋放,佔用了8MB。
如果展開 (string)
Constructor,會看到許多單獨的記憶體分配。選擇某一個單獨的分配,下面的 retainers 會吸引我們的注意。
我們已選擇的分配是數組的一部分,數組關聯到 window
物件的 x
變數。這裡展示了從巨大物件到無法回收的 root(window
)的完整路徑。我們已經找到了潛在的洩漏以及它的來源。
我們的範例還算簡單,只洩漏了少量的 DOM 節點,利用以上提到的快照很容易發現。對於更大型的網站,Chrome 還提供了 Record Heap Allocations 功能。
Record heap allocations 找記憶體洩漏
回到 Chrome Dev Tools 的 profiles 標籤,點擊 Record Heap Allocations。工具運行的時候,注意頂部的藍條,代表了記憶體分配,每一秒有大量的記憶體分配。運轉幾秒以後停止。
上圖中可以看到工具的殺手鐧:選擇某一時間線,可以看到這個時間段的記憶體分配狀況。盡可能選擇接近峰值的時間線,下面的列表僅顯示了三種constructor:其一是洩漏最嚴重的(string)
,下一個是關聯的DOM 分配,最後一個是 Text
constructor(DOM 葉子節點包含的文字)。
從清單中選擇一個 HTMLpElement
constructor,然後選擇 Allocation stack
。
現在知道元素被分配到哪裡了吧(grow
-> createSomeNodes
),仔細觀察圖中的時間線,發現HTMLpElement
constructor 呼叫了許多次,表示記憶體一直被佔用,無法被GC 回收,我們知道了這些物件被分配的確切位置(createSomeNodes
)。回到程式碼本身,探討下如何修復記憶體外洩吧。
另一個有用的特性
在 heap allocations 的結果區域,選擇 Allocation。
這個視圖呈現了記憶體分配相關的功能列表,我們立刻看到了 grow
和 createSomeNodes
。當選擇 grow
時,看看相關的 object constructor,清楚地看到 (string)
, HTMLpElement
和 Text
洩漏了。
結合以上提到的工具,可以輕鬆找到記憶體外洩。
【推薦學習:javascript高階教學】
#以上是如何處理javascript記憶體洩露的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數
