本篇文章給大家?guī)?lái)了關(guān)于JavaScript中內(nèi)存泄露的相關(guān)知識(shí),其中包括內(nèi)存泄露是什么,那些情況會(huì)引起內(nèi)存泄露等相關(guān)問(wèn)題,希望對(duì)大家有幫助。
js 內(nèi)存泄漏
什么是內(nèi)存泄漏?
程序的運(yùn)行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運(yùn)行時(shí)(runtime)就必須供給內(nèi)存。
對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程(daemon),必須及時(shí)釋放不再用到的內(nèi)存。否則,內(nèi)存占用越來(lái)越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進(jìn)程崩潰。
不再用到的內(nèi)存,沒(méi)有及時(shí)釋放,就叫做內(nèi)存泄漏(memory leak)。
有些語(yǔ)言(比如 C 語(yǔ)言)必須手動(dòng)釋放內(nèi)存,程序員負(fù)責(zé)內(nèi)存管理。
char * buffer;buffer = (char*) malloc(42);// Do something with bufferfree(buffer);
上面是 C 語(yǔ)言代碼,malloc方法用來(lái)申請(qǐng)內(nèi)存,使用完畢之后,必須自己用free方法釋放內(nèi)存。
這很麻煩,所以大多數(shù)語(yǔ)言提供自動(dòng)內(nèi)存管理,減輕程序員的負(fù)擔(dān),這被稱(chēng)為"垃圾回收機(jī)制"(garbage collector)。
雖然前端有垃圾回收機(jī)制,但當(dāng)某塊無(wú)用的內(nèi)存,卻無(wú)法被垃圾回收機(jī)制認(rèn)為是垃圾時(shí),也就發(fā)生內(nèi)存泄漏了。
哪些情況會(huì)引起內(nèi)存泄漏
1. 意外的全局變量
全局變量的生命周期最長(zhǎng),直到頁(yè)面關(guān)閉前,它都存活著,所以全局變量上的內(nèi)存一直都不會(huì)被回收。
當(dāng)全局變量使用不當(dāng),沒(méi)有及時(shí)回收(手動(dòng)賦值 null),或者拼寫(xiě)錯(cuò)誤等將某個(gè)變量掛載到全局變量時(shí),也就發(fā)生內(nèi)存泄漏了。
2. 遺忘的定時(shí)器
setTimeout 和 setInterval 是由瀏覽器專(zhuān)門(mén)線(xiàn)程來(lái)維護(hù)它的生命周期,所以當(dāng)在某個(gè)頁(yè)面使用了定時(shí)器,當(dāng)該頁(yè)面銷(xiāo)毀時(shí),沒(méi)有手動(dòng)去釋放清理這些定時(shí)器的話(huà),那么這些定時(shí)器還是存活著的。
也就是說(shuō),定時(shí)器的生命周期并不掛靠在頁(yè)面上,所以當(dāng)在當(dāng)前頁(yè)面的 js 里通過(guò)定時(shí)器注冊(cè)了某個(gè)回調(diào)函數(shù),而該回調(diào)函數(shù)內(nèi)又持有當(dāng)前頁(yè)面某個(gè)變量或某些 DOM 元素時(shí),就會(huì)導(dǎo)致即使頁(yè)面銷(xiāo)毀了,由于定時(shí)器持有該頁(yè)面部分引用而造成頁(yè)面無(wú)法正常被回收,從而導(dǎo)致內(nèi)存泄漏了。
如果此時(shí)再次打開(kāi)同個(gè)頁(yè)面,內(nèi)存中其實(shí)是有雙份頁(yè)面數(shù)據(jù)的,如果多次關(guān)閉、打開(kāi),那么內(nèi)存泄漏會(huì)越來(lái)越嚴(yán)重。而且這種場(chǎng)景很容易出現(xiàn),因?yàn)槭褂枚〞r(shí)器的人很容易遺忘清除。
3. 使用不當(dāng)?shù)拈]包
函數(shù)本身會(huì)持有它定義時(shí)所在的詞法環(huán)境的引用,但通常情況下,使用完函數(shù)后,該函數(shù)所申請(qǐng)的內(nèi)存都會(huì)被回收了。
但當(dāng)函數(shù)內(nèi)再返回一個(gè)函數(shù)時(shí),由于返回的函數(shù)持有外部函數(shù)的詞法環(huán)境,而返回的函數(shù)又被其他生命周期東西所持有,導(dǎo)致外部函數(shù)雖然執(zhí)行完了,但內(nèi)存卻無(wú)法被回收。
4. 遺漏的 DOM 元素
DOM 元素的生命周期正常是取決于是否掛載在 DOM 樹(shù)上,當(dāng)從 DOM 樹(shù)上移除時(shí),也就可以被銷(xiāo)毀回收了
但如果某個(gè) DOM 元素,在 js 中也持有它的引用時(shí),那么它的生命周期就由 js 和是否在 DOM 樹(shù)上兩者決定了,記得移除時(shí),兩個(gè)地方都需要去清理才能正?;厥账?。
5. 網(wǎng)絡(luò)回調(diào)
某些場(chǎng)景中,在某個(gè)頁(yè)面發(fā)起網(wǎng)絡(luò)請(qǐng)求,并注冊(cè)一個(gè)回調(diào),且回調(diào)函數(shù)內(nèi)持有該頁(yè)面某些內(nèi)容,那么,當(dāng)該頁(yè)面銷(xiāo)毀時(shí),應(yīng)該注銷(xiāo)網(wǎng)絡(luò)的回調(diào),否則,因?yàn)榫W(wǎng)絡(luò)持有頁(yè)面部分內(nèi)容,也會(huì)導(dǎo)致頁(yè)面部分內(nèi)容無(wú)法被回收。
如何監(jiān)控內(nèi)存泄漏
內(nèi)存泄漏是可以分成兩類(lèi)的,一種是比較嚴(yán)重的,泄漏的就一直回收不回來(lái)了,另一種嚴(yán)重程度稍微輕點(diǎn),就是沒(méi)有及時(shí)清理導(dǎo)致的內(nèi)存泄漏
,一段時(shí)間后還是可以被清理掉。
不管哪一種,利用開(kāi)發(fā)者工具抓到的內(nèi)存圖,應(yīng)該都會(huì)看到一段時(shí)間內(nèi),內(nèi)存占用不斷的直線(xiàn)式下降,這是因?yàn)椴粩喟l(fā)生 GC,也就是垃圾回收導(dǎo)致的。
內(nèi)存不足會(huì)造成不斷 GC,而 GC 時(shí)是會(huì)阻塞主線(xiàn)程
的,所以會(huì)影響到頁(yè)面性能,造成卡頓,所以?xún)?nèi)存泄漏問(wèn)題還是需要關(guān)注的。
場(chǎng)景一:在某個(gè)函數(shù)內(nèi)申請(qǐng)一塊內(nèi)存,然后該函數(shù)在短時(shí)間內(nèi)不斷被調(diào)用
// 點(diǎn)擊按鈕,就執(zhí)行一次函數(shù),申請(qǐng)一塊內(nèi)存startBtn.addEventListener("click", function() { var a = new Array(100000).fill(1); var b = new Array(20000).fill(1);});
一個(gè)頁(yè)面能夠使用的內(nèi)存是有限的,當(dāng)內(nèi)存不足時(shí),就會(huì)觸發(fā)垃圾回收機(jī)制去回收沒(méi)用的內(nèi)存。
而在函數(shù)內(nèi)部使用的變量都是局部變量,函數(shù)執(zhí)行完畢,這塊內(nèi)存就沒(méi)用可以被回收了。
所以當(dāng)我們短時(shí)間內(nèi)不斷調(diào)用該函數(shù)時(shí),可以發(fā)現(xiàn),函數(shù)執(zhí)行時(shí),發(fā)現(xiàn)內(nèi)存不足,垃圾回收機(jī)制工作,回收上一個(gè)函數(shù)申請(qǐng)的內(nèi)存,因?yàn)樯蟼€(gè)函數(shù)已經(jīng)執(zhí)行結(jié)束了,內(nèi)存無(wú)用可被回收了。
所以圖中呈現(xiàn)內(nèi)存使用量的圖表就是一條橫線(xiàn)過(guò)去,中間出現(xiàn)多處豎線(xiàn),其實(shí)就是表示內(nèi)存清空,再申請(qǐng),清空再申請(qǐng),每個(gè)豎線(xiàn)的位置就是垃圾回收機(jī)制工作以及函數(shù)執(zhí)行又申請(qǐng)的時(shí)機(jī)。
場(chǎng)景二:在某個(gè)函數(shù)內(nèi)申請(qǐng)一塊內(nèi)存,然后該函數(shù)在短時(shí)間內(nèi)不斷被調(diào)用,但每次申請(qǐng)的內(nèi)存,有一部分被外部持有。
// 點(diǎn)擊按鈕,就執(zhí)行一次函數(shù),申請(qǐng)一塊內(nèi)存var arr = [];startBtn.addEventListener("click", function() { var a = new Array(100000).fill(1); var b = new Array(20000).fill(1); arr.push(b);});
看一下跟第一張圖片有什么區(qū)別?
不再是一條橫線(xiàn)了吧,而且橫線(xiàn)中的每個(gè)豎線(xiàn)的底部也不是同一水平了吧。
其實(shí)這就是內(nèi)存泄漏
了。
我們?cè)诤瘮?shù)內(nèi)申請(qǐng)了兩個(gè)數(shù)組內(nèi)存,但其中有個(gè)數(shù)組卻被外部持有,那么,即使每次函數(shù)執(zhí)行完,這部分被外部持有的數(shù)組內(nèi)存也依舊回收不了,所以每次只能回收一部分內(nèi)存。
這樣一來(lái),當(dāng)函數(shù)調(diào)用次數(shù)增多時(shí),沒(méi)法回收的內(nèi)存就越多,內(nèi)存泄漏的也就越多,導(dǎo)致內(nèi)存使用量一直在增長(zhǎng)
另外,也可以使用 performance monitor
工具,在開(kāi)發(fā)者工具里找到