程序的運行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運行時就必須供給內(nèi)存。對于持續(xù)運行的服務進程,必須及時釋放不再用到的內(nèi)存。否則,內(nèi)存占用越來越高,輕則影響系統(tǒng)性能,重則導致進程崩潰。不再用到的內(nèi)存,沒有及時釋放,就是內(nèi)存泄漏(memory leak)。而內(nèi)存溢出(out of menory),指的是程序需求的內(nèi)存,超出了系統(tǒng)所能分配的范圍。
一、內(nèi)存泄漏(memory leak)
內(nèi)存泄漏是每個開發(fā)者最終都要面對的問題,它是許多問題的根源:反應遲緩,崩潰,高延遲,以及其他應用問題。
1. 概念
應用程序不再需要占用內(nèi)存的時候,由于某些原因,內(nèi)存沒有被操作系統(tǒng)或可用內(nèi)存池回收。
2. 常見內(nèi)存泄漏
意外的全局變量JS中對于未聲明的變量,會在全局范圍中創(chuàng)建一個新的變量來對其進行引用。在瀏覽器中,全局對象是window。
function fn() {
a = 3
console.log(a)
}
fn()
沒有及時清理的循環(huán)計時器
var intervalId = setInterval(function () {
console.log( '----')
}, 1000)
//未清除當前循環(huán)定時器 cLearInterval(intervaLId)
閉包未釋放閉包(closure)是一個函數(shù)以及其捆綁的周邊環(huán)境狀態(tài)(lexical environment,詞法環(huán)境)的引用的組合。換而言之,閉包讓開發(fā)者可以從內(nèi)部函數(shù)訪問外部函數(shù)的作用域。在 JAVAScript 中,閉包會隨著函數(shù)的創(chuàng)建而被同時創(chuàng)建。當閉包被使用,內(nèi)部函數(shù)使用了外部函數(shù)的變量,就會造成內(nèi)存泄漏。
function fn1() {
var a = 1
function fn2() {
var b = ++a
console.log(b)
}
return fn2
}
var f = fn1()
f()
//釋放閉包 f = null
3. 怎樣避免內(nèi)存泄漏
減少不必要的全局變量,或者生命周期較長的對象,及時對無用的數(shù)據(jù)進行垃圾回收;
注意程序邏輯,避免“死循環(huán)”之類的;
避免創(chuàng)建過多的對象 原則:不用了的東西要及時歸還。
二、垃圾回收機制
因為不是所有無用對象內(nèi)存都可以被垃圾回收機制回收的,那當不再用到的內(nèi)存,沒有及時回收時,我們叫內(nèi)存泄漏。那么我們接下來了解下垃圾回收機制。
1.垃圾如何產(chǎn)生?又如何判斷垃圾?
簡單來說垃圾就是程序不用的內(nèi)存或者是之前用過了,以后不會再用的內(nèi)存空間。如何判斷垃圾就是看這個對象能否被訪問,那如何知道對象能否被訪問?有一個專業(yè)的詞叫可達性。根據(jù)對象是否可達來判斷。可達就不需要被回收,不可達就需要被回收。
let obj= {name: 'zs'}
obj = [1, 2, 3]
在js中數(shù)據(jù)分基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,引用數(shù)據(jù)類型在棧中保存的是引用,實際是存儲在堆中的。在上面的例子中我們首先創(chuàng)建了一個obj變量指向?qū)ο髙name: 'zs'},然后又把obj指向了新的數(shù)組[1, 2, 3],所以之前的{name: 'zs'}就不可能被訪問到了(沒有了可達性),就變成了垃圾。
2.為什么要垃圾回收?
從上面的例子可以看出產(chǎn)生了垃圾就會導致浪費內(nèi)存空間,一個兩個還好,多了的話我們的程序可能會越來越卡頓,到最后崩潰。所以就需要垃圾回收機制來幫我們自動清理沒用的垃圾,釋放出更多的內(nèi)存來給當前程序使用,這樣程序就會一直流暢的運行下去。
3.垃圾回收的方式
垃圾回收機制里面最常用的兩個方式就是標記清除法和引用計數(shù)法。
標記清除法這是目前瀏覽器大多基于標記清除法。我們可以分為兩個階段:標記:從根節(jié)點遍歷為每個可以訪問到的對象都打上一個標記,表示該對象可達。清除:在沒有可用分塊時,對堆內(nèi)存遍歷,若沒有被標記為可達對象就將其回收。
引用計數(shù)法引用計數(shù)法就是追蹤每個變量被引用的次數(shù),當引用數(shù)為0將可以被回收掉。
三、內(nèi)存溢出(out of menory)
一種程序運行出現(xiàn)的錯誤,當程序運行需要的內(nèi)存超過剩余內(nèi)存時就會拋出內(nèi)存溢出的錯誤。
1.概念
程序要求的內(nèi)存,超出了系統(tǒng)所能分配的范圍。
2.舉例
var arr=[]
for (let i = 0; i < 1000000; i++) {
arr[i] = new Array(1000000)
}
//網(wǎng)頁直接崩潰,錯誤代碼:Out of Memory
四、實際項目中出現(xiàn)的內(nèi)存溢出
VUE項目開發(fā)時間長了,隨著項目越來越大,打包的時間也相應的變長,打包時的內(nèi)存也增多了。這時候產(chǎn)生了一個問題,在發(fā)布項目的時候,會出現(xiàn)類似如下錯誤的提示。
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation fAIled - JavaScript heap out of memory
out of memory內(nèi)存溢出,使用內(nèi)存時只能使用部分內(nèi)存(64位系統(tǒng):1.4 GB,32位系統(tǒng):0.7 GB),這個時候,如果前端項目非常的龐大,Webpack編譯時就會占用很多的系統(tǒng)資源,如果超出了V8引擎對默認的內(nèi)存限制大小時,就會產(chǎn)生內(nèi)存溢出的錯誤。
解決辦法如下:
// 安裝兩個npm包 :cross-env increase-memory-limit
// npm install cross-env increase-memory-limit
// 再package.json中添加fix-memory-limit命令
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit",
},
/*
操作完以上步驟之后,可能會報錯 “node –max-old-space-size=4096不是內(nèi)部或外部命令``”該問題的解決辦法:
在項目的node_modules.bin下面找到所有的*.cmd文件,
在ENDLOCAL語句的上邊一行,修改"%_prog%" 改為 %_prog%, 去掉雙引號。
*/
// LIMIT是你想分配的內(nèi)存大小,然后執(zhí)行npm run fix-memory-limit。
再執(zhí)行npm run serve重新啟動項目,就不會再內(nèi)存溢出了。
五、總結(jié)
內(nèi)存泄漏積累過多最終會導致內(nèi)存溢出,因為系統(tǒng)中的內(nèi)存是有限的,如果過度占用資源而不及時釋放,最后會導致內(nèi)存不足,從而無法給所需要存儲的數(shù)據(jù)提供足夠的內(nèi)存,從而導致內(nèi)存溢出。