詳解JavaScript的垃圾回收機(jī)制
為什么需要垃圾回收(GC)
- 程序和人一樣,生活時(shí)間長(zhǎng)了會(huì)產(chǎn)生垃圾,程序在運(yùn)行過(guò)程中也會(huì)產(chǎn)生垃圾,垃圾積攢過(guò)多后,會(huì)導(dǎo)致程序運(yùn)行速度變慢。
- 在JavaScript中的字符串、對(duì)象、數(shù)組等數(shù)據(jù)的內(nèi)存是不固定的,只有真正使用的時(shí)候才會(huì)動(dòng)態(tài)分配內(nèi)存。
- 這些數(shù)據(jù)所占的內(nèi)存在不使用時(shí),需要進(jìn)行釋放,以便再次使用,否者在可用內(nèi)存耗盡造成程序崩潰。
什么是垃圾回收
垃圾回收機(jī)制也稱(chēng)Garbage Collection
簡(jiǎn)稱(chēng)GC。在JavaScript中擁有自動(dòng)的垃圾回收機(jī)制,通過(guò)一些回收算法,找出不再使用引用的變量或?qū)傩?,由JS引擎按照固定時(shí)間間隔周期性的釋放其所占的內(nèi)存空間。在C/C++中需要程序員手動(dòng)完成垃圾回收。
垃圾產(chǎn)生
當(dāng)一個(gè)對(duì)象沒(méi)有任何的變量或?qū)傩詫?duì)它進(jìn)行引用,此時(shí)我們將永遠(yuǎn)無(wú)法操作該對(duì)象,這種對(duì)象就是一個(gè)垃圾,這種對(duì)象過(guò)多會(huì)占用大量的內(nèi)存空間,導(dǎo)致程序變慢。
例如:
這里我先聲明了一個(gè)Person變量,它引用了對(duì)象{name: "江流",age: 20},接著我又將這個(gè)Person變量指向了另一個(gè)對(duì)象{name: "心猿", age: 5000},那么之前被引用的對(duì)象,現(xiàn)在就成了無(wú)用對(duì)象,也永遠(yuǎn)無(wú)法使用操作該對(duì)象,這種對(duì)象就是一個(gè)垃圾。
這種垃圾對(duì)象過(guò)多,就會(huì)占用大量空間,如果一直不釋放就會(huì)影響系統(tǒng)性能,重則導(dǎo)致程序崩潰,所以就需要垃圾回收釋放這部分內(nèi)存。
這個(gè)過(guò)程我們不需要也不能進(jìn)行垃圾回收的操作。
我們只需要的是將不再使用的對(duì)象設(shè)置為null即可。
垃圾回收策略
JavaScript 中主要的內(nèi)存管理概念是可達(dá)性。大概意思是以某種方式可以訪問(wèn)到或者可以使用的值,它們就是需要保存在內(nèi)存中,無(wú)法訪問(wèn),也無(wú)法使用的值,則需要被垃圾回收機(jī)制回收。
垃圾回收過(guò)程是不實(shí)時(shí)進(jìn)行的,因?yàn)镴avaScript是一門(mén)單線程的語(yǔ)言,每次執(zhí)行垃圾回收,會(huì)使程序應(yīng)用邏輯暫停,執(zhí)行完垃圾后回收在執(zhí)行應(yīng)用邏輯,這種行為稱(chēng)為全停頓,所以一般垃圾回收會(huì)在cpu閑時(shí)進(jìn)行。
如何通過(guò)某種方式找到所謂的垃圾,是垃圾回收的重點(diǎn),所以下面常見(jiàn)的算法策略,不過(guò)這里只說(shuō)前兩種:
- 引用計(jì)數(shù)算法
- 標(biāo)記清除算法
- 標(biāo)記整理
- 分代回收
引用計(jì)數(shù)標(biāo)記
策略思想:
- 跟蹤記錄每個(gè)變量值被使用的次數(shù)
- 當(dāng)聲明一個(gè)變量并且將一個(gè)引用類(lèi)型數(shù)據(jù)賦值給這個(gè)變量的時(shí)候,這個(gè)引用類(lèi)型數(shù)據(jù)的引用次數(shù)就標(biāo)記為 1
- 如果當(dāng)這個(gè)引用類(lèi)型數(shù)據(jù)又賦值給另一個(gè)變量,那么引用次數(shù)就+1
- 如果變量被其他的值覆蓋,則引用次數(shù)-1
- 當(dāng)這個(gè)引用類(lèi)型數(shù)據(jù)的引用次數(shù)變?yōu)?的時(shí)候,這個(gè)變量就沒(méi)有被使用了,也無(wú)法訪問(wèn),垃圾回收器就會(huì)在執(zhí)行時(shí),銷(xiāo)毀引用次數(shù)為0的引用類(lèi)型數(shù)據(jù),回收其所占用的內(nèi)存空間。
例如:
let a = { name: "江流", age: 20 }; //此時(shí)該對(duì)象的引用計(jì)數(shù)標(biāo)記為1(a 引用) let b = a; //此時(shí)對(duì)象的引用計(jì)數(shù)標(biāo)記為2(a、b 引用) a = null; //此時(shí)對(duì)象的引用計(jì)數(shù)標(biāo)記為1((b 引用)) b = null; //此時(shí)對(duì)象的引用計(jì)數(shù)標(biāo)記為0(無(wú)變量引用) ... //等待GC 回收此對(duì)象
但是這種方式有個(gè)很?chē)?yán)重的問(wèn)題 – 循環(huán)引用
循環(huán)引用引發(fā)的問(wèn)題
在一個(gè)函數(shù)中,對(duì)象A的屬性指向?qū)ο驜,對(duì)象B的屬性指向?qū)ο驛,這個(gè)函數(shù)在執(zhí)行完,對(duì)象A和B的計(jì)數(shù)器也不會(huì)為0,影響了正常的GC。
例如下面的例子:
function test() { let A = new Object(); let B = new Object(); A.pointer= B; B.pointer = A; } test();
當(dāng)對(duì)象A和對(duì)象B的屬性相互引用這,按照引用計(jì)數(shù)策略,他們的引用計(jì)數(shù)都是為2,但是在test()執(zhí)行完成后,在函數(shù)執(zhí)行完,函數(shù)作用域中的數(shù)據(jù)對(duì)象A和對(duì)象B都應(yīng)該被GC銷(xiāo)毀掉。
如果執(zhí)行多次,將會(huì)造成嚴(yán)重的內(nèi)存泄漏。
解決方法
在函數(shù)結(jié)束時(shí),將其指向null
//切斷引用關(guān)系 A = null; B = null;
引用計(jì)數(shù)算法的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 引用計(jì)數(shù)為零時(shí),發(fā)現(xiàn)垃圾立即回收
- 最大限度減少程序暫停
缺點(diǎn):
- 無(wú)法回收循環(huán)引用的對(duì)象
- 空間開(kāi)銷(xiāo)比較大
標(biāo)記清除算法
核心思想
分標(biāo)記和清除兩個(gè)階段完成。
大概過(guò)程:
- 垃圾收集器在運(yùn)行時(shí)會(huì)給內(nèi)存中所有的變量都加上一個(gè)標(biāo)記,假設(shè)內(nèi)存中所有的對(duì)象全部是垃圾,全部標(biāo)記為0
- 然后從各個(gè)根對(duì)象開(kāi)始遍歷,把不是垃圾的節(jié)點(diǎn)改成1
- 清理所有標(biāo)記為0的垃圾,銷(xiāo)毀并回收它們所占用的內(nèi)存空間
- 最后把所有內(nèi)存中對(duì)象標(biāo)記修改為0,等待下一輪的垃圾回收
標(biāo)記清除算法優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡(jiǎn)單,標(biāo)記情況無(wú)非是打與不打的兩種情況,通過(guò)二進(jìn)制(0和1)就可以為其標(biāo)記。
- 能夠回收循環(huán)引用的對(duì)象
- 是v8引擎使用最多的算法。
缺點(diǎn):
在清除垃圾之后,剩余對(duì)象的內(nèi)存位置是不變的,就會(huì)導(dǎo)致空閑內(nèi)存空間不連續(xù)。這樣就出現(xiàn)了內(nèi)存碎片,并且由于剩余空間不是整塊,就需要內(nèi)存分配的問(wèn)題。
標(biāo)記整理算法
標(biāo)記整理(Mark-Compact)算法,就是可以有效的解決,它是在標(biāo)記結(jié)束后標(biāo)記整理算法會(huì)將不需要清理的對(duì)象向內(nèi)存一端移動(dòng),最后清理邊界的內(nèi)存。
V8引擎的垃圾回收
- V8引擎的垃圾回收采用標(biāo)記清除法與分代回收法
- 分為新生代和老生代
針對(duì)不同對(duì)象采用不同算法:
(1)新生代:對(duì)象的存活時(shí)間較短。新生對(duì)象或只經(jīng)過(guò)一次垃圾回收的對(duì)象。
(2)老生代:對(duì)象存活時(shí)間較長(zhǎng)。經(jīng)歷過(guò)一次或多次垃圾回收的對(duì)象。
回收新生代對(duì)象
回收新生代對(duì)象主要采用復(fù)制算法(Scavenge 算法)加標(biāo)記整理算法。而Scavenge 算法的具體實(shí)現(xiàn),主要采用了Cheney算法。
對(duì)象晉升機(jī)制
一輪GC還存活的新生代需要晉升。
回收老生代對(duì)象
回收老生代對(duì)象主要采用標(biāo)記清除、標(biāo)記整理、增量標(biāo)記算法,主要使用標(biāo)記清除算法,只有在內(nèi)存分配不足時(shí),采用標(biāo)記整理算法。
- 首先使用標(biāo)記清除完成垃圾空間的回收;
- 采用標(biāo)記整理進(jìn)行空間優(yōu)化;
- 采用增量標(biāo)記進(jìn)行效率優(yōu)化;
參考文檔:
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- JS數(shù)據(jù)類(lèi)型(基本數(shù)據(jù)類(lèi)型、引用數(shù)據(jù)類(lèi)型)及堆和棧的區(qū)別分析
- 詳解JavaScript棧內(nèi)存與堆內(nèi)存
- JavaScript錯(cuò)誤處理和堆棧追蹤詳解
- JavaScript實(shí)現(xiàn)顯示函數(shù)調(diào)用堆棧的方法
- JS實(shí)現(xiàn)隊(duì)列與堆棧的方法
- 對(duì)于js垃圾回收機(jī)制的理解
- 跟我學(xué)習(xí)javascript的垃圾回收機(jī)制與內(nèi)存管理
- 前端高頻面試題之JS中堆和棧的區(qū)別和瀏覽器的垃圾回收機(jī)制
相關(guān)文章
JS中的數(shù)組轉(zhuǎn)變成JSON格式字符串的方法
這篇文章主要介紹了JS中的數(shù)組轉(zhuǎn)變成JSON格式字符串的方法,需要的朋友可以參考下2017-05-05給所有的超級(jí)練級(jí)都加上onmousemove時(shí)間的js代碼
給所有的超級(jí)練級(jí)都加上onmousemove時(shí)間的js代碼...2007-08-08利用MutationObserver實(shí)現(xiàn)計(jì)算首屏?xí)r間
在前端開(kāi)發(fā)中,優(yōu)化頁(yè)面性能是至關(guān)重要的,計(jì)算首屏?xí)r間是衡量網(wǎng)頁(yè)性能的重要指標(biāo),本文將介紹如何使用MutationObserver來(lái)獲取首屏?xí)r間的最佳實(shí)踐,感興趣的可以了解下2023-07-07JavaScript Reflect Metadata實(shí)現(xiàn)詳解
這篇文章主要介紹了JavaScript Reflect Metadata實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12javascript XML數(shù)據(jù)顯示為HTML一例
通過(guò)Javascript把xml轉(zhuǎn)換成html格式輸出一列2008-12-12JavaScript中的各種寬高屬性的實(shí)現(xiàn)
這篇文章主要介紹了JavaScript中的各種寬高屬性的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05