要瞭解記憶體泄漏與記憶體溢出,首先需要瞭解記憶體是怎麼分配的,故此,本文將按照以下幾節闡述: 記憶體管理 垃圾回收· 記憶體泄漏 記憶體管理 JavaScript 是在創建變數(對象,字元串等)時自動進行了分配記憶體,並且在不使用它們時“自動”釋放。釋放的過程稱為垃圾回收。這個“自動”是混亂的根源,並讓 Java ...
要瞭解記憶體泄漏與記憶體溢出,首先需要瞭解記憶體是怎麼分配的,故此,本文將按照以下幾節闡述:
- 記憶體管理
- 垃圾回收·
- 記憶體泄漏
記憶體管理
JavaScript 是在創建變數(對象,字元串等)時自動進行了分配記憶體,並且在不使用它們時“自動”釋放。釋放的過程稱為垃圾回收。這個“自動”是混亂的根源,並讓 JavaScript(和其他高級語言)開發者錯誤的感覺他們可以不關心記憶體管理。
記憶體生命周期基本是一致的:
- 分配你所需要的記憶體
- 使用分配到的記憶體(讀、寫)
- 不需要時將其釋放\歸還
在JavaScript中第一步和第三步都是隱含的
為了不讓程式員費心分配記憶體,JavaScript 在定義變數(值的初始化)時就完成了記憶體分配。
記憶體分配
var n = 123; // 給數值變數分配記憶體
var s = "azerty"; // 給字元串分配記憶體
var o = {
a: 1,
b: null
};
// 給對象及其包含的值分配記憶體
// 給數組及其包含的值分配記憶體(就像對象一樣)
var a = [1, null, "abra"];
function f(a){
return a + 2;
}
// 給函數(可調用的對象)分配記憶體
// 函數表達式也能分配一個對象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);
有些函數調用結果是分配對象記憶體:
var d = new Date(); // 分配一個 Date 對象
var e = document.createElement('div'); // 分配一個 DOM 元素
使用值的過程實際上是對分配記憶體進行讀取與寫入的操作。讀取與寫入可能是寫入一個變數或者一個對象的屬性值,甚至傳遞函數的參數。
當分配的記憶體不再使用時釋放它
大多數記憶體管理的問題都在這個階段。在這裡最艱難的任務是找到“哪些被分配的記憶體確實已經不再需要了”。它往往要求開發人員來確定在程式中哪一塊記憶體不再需要並且釋放它。
高級語言解釋器嵌入了“垃圾回收器”,它的主要工作是跟蹤記憶體的分配和使用,以便當分配的記憶體不再使用時,自動釋放它。這隻能是一個近似的過程,因為要知道是否仍然需要某塊記憶體是無法判定的(無法通過某種演算法解決)。
垃圾回收
垃圾回收演算法主要依賴於引用的概念。在記憶體管理的環境中,一個對象如果有訪問另一個對象的許可權(隱式或者顯式),叫做一個對象引用另一個對象。例如,一個 Javascript 對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。
引用計數垃圾回收
最初級的垃圾回收,此演算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。
限制:無法處理迴圈引用的事例。在下麵的例子中,兩個對象被創建,並互相引用,形成了一個迴圈。它們被調用之後會離開函數作用域,所以它們已經沒有用了,可以被回收了。然而,引用計數演算法考慮到它們互相都有至少一次引用,所以它們不會被回收。記憶體發生泄漏
標記清除演算法
這個演算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”。
這個演算法假定設置一個叫做根(root)的對象(在 Javascript 里,根是全局對象)。垃圾回收器將定期從根開始,找所有從根開始引用的對象,然後找這些對象引用的對象……從根開始,垃圾回收器將找到所有可以獲得的對象和收集所有不能獲得的對象。
這個演算法比前一個要好,因為“有零引用的對象”總是不可獲得的,但是相反卻不一定,參考“迴圈引用”。
從 2012 年起,所有現代瀏覽器都使用了標記 - 清除垃圾回收演算法。所有對 JavaScript 垃圾回收演算法的改進都是基於標記 - 清除演算法的改進,並沒有改進標記 - 清除演算法本身和它對“對象是否不再需要”的簡化定義。
迴圈引用只要無法從根出發獲得對象就會被清除
限制:無法從根對象查詢到的對象都會被清除
記憶體泄漏
記憶體泄漏(Memory Leak)是指程式中已動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式運行速度減慢甚至系統崩潰等嚴重後果。(程式某個未使用的變數或者方法,長期占用記憶體不會釋放,導致記憶體堆積浪費)引用計數方式對 DOM 對象進行垃圾回收。該方式常常造成對象被迴圈引用時記憶體發生泄漏
記憶體溢出:“記憶體溢出(Out Of Memory,簡稱OOM)是指應用系統中存在無法回收的記憶體或使用的記憶體過多,最終使得程式運行要用到的記憶體大於能提供的最大記憶體。此時程式就運行不了,系統會提示記憶體溢出,有時候會自動關閉軟體,重啟電腦或者軟體後釋放掉一部分記憶體又可以正常運行該軟體,而由系統配置、數據流、用戶代碼等原因而導致的記憶體溢出錯誤,即使用戶重新執行任務依然無法避免。”(因為某些原因,程式使用的記憶體大於硬體提供的記憶體,導致記憶體超出了)
————————————————
記憶體泄漏可能的原因
一、全局變數引用
這種問題發生的頻率一般較小, 我覺得應該沒人把全局變數綁定到組件上,或者使用變數沒有聲明。
需要註意的是,要儘可能少地聲明全局變數,因為GCRoots的原因,很容易會在查看引用鏈的時候鏈接到window上的屬性,造成干擾。
二、定時器未清除
如果你不確定是不是定時器導致的問題,可以打開【開發者工具】,在Chrome中會有警告
所以養成好習慣,setTimeout使用一定要清除
const timer = setTimeOut(() => {
this.resize()
clearTimeOut(timer)
})
複製代碼
setInterval也是一樣的處理,不過不太建議在代碼里使用setInterval,主要是不太好控制和錯誤捕獲不到。
在平時開發時,可以留意下控制台裡面Chrome發出的warning,去看看背後的原因是什麼,一般和性能都是有關係的,這樣會讓你知道怎麼樣寫代碼對於瀏覽器來說是沒有問題的。
三、事件未清除
這也是很常見的問題,養成好習慣。
// mounted
window.addEventListener('resize', this.resize)
// beforeDestory
widnow.removeEventListener('resize', this.resize)
複製代碼
這裡需要註意最好不要用on的方式去綁定事件,覆蓋事件以及解綁處理起來會很麻煩。
四、console
生成環境最好不要有console.log,如果有,最好是文字解釋或者格式化後的JSON字元串,不要引用當前實例的數據。
五、未添加到Document的dom
function download(url) {
const a = document.createElement('a')
a.href = url
a.click()
}
如果不手動將a置為null,那麼該dom會一直造成泄漏
複製代碼
六、使用一些插件時,未銷毀由該插件生成的dom
比較常用的如Sortable
const sortableInstance = Sortable.create(……)
sortableInstance.destroy()
複製代碼
另外我們自己寫的一些函數,如果生成了dom,或者引用了dom,都要記得銷毀。
let loading = this.$loading({
……,
target: this.$refs.table.el
})
……
loading.close()
loading = null
參考引用:
https://juejin.cn/post/7005110828593020965
https://blog.csdn.net/qq_36359674/article/details/123357844