JavaScript垃圾回收機(jī)制原理總結(jié)深入探究
1. 垃圾為何要產(chǎn)生并回收
當(dāng)我們寫(xiě)代碼時(shí)創(chuàng)建一個(gè)基本類型、對(duì)象、函數(shù)等,都是需要占用內(nèi)存的,JavaScript基本數(shù)據(jù)類型存儲(chǔ)在棧內(nèi)存中,引用數(shù)據(jù)類型存儲(chǔ)在堆內(nèi)存中,但是引用數(shù)據(jù)類型會(huì)在棧內(nèi)存中存儲(chǔ)一個(gè)實(shí)際對(duì)象的引用。
比如說(shuō)我們創(chuàng)建了一個(gè)person
對(duì)象,然后將person
對(duì)象重新賦值:
var person = { name: "橘貓吃不胖", age: 2 } person = [1, 2, 3]; console.log(person); // [ 1, 2, 3 ]
那么原本堆內(nèi)存給person
對(duì)象開(kāi)辟了一個(gè)空間來(lái)存放,棧內(nèi)存中存放了該引用的地址,但是在下一步中,person
對(duì)象成為了一個(gè)數(shù)組,也就是說(shuō)引用地址從原來(lái)的對(duì)象變成了數(shù)組,原來(lái)的引用關(guān)系就沒(méi)有了,那么這時(shí)原來(lái)的對(duì)象在堆內(nèi)存中就會(huì)成為一個(gè)垃圾。
產(chǎn)生的垃圾如果很多,而且一直不清理,堆積起來(lái),就會(huì)影響系統(tǒng)的性能,甚至可能造成系統(tǒng)崩潰。
2. 垃圾回收機(jī)制
JavaScript中主要的內(nèi)存管理概念是可達(dá)性。
那什么是可達(dá)性呢,比如說(shuō)定義一個(gè)對(duì)象:
let person = { name: "橘貓吃不胖", age: 2 } console.log(person.name, person.age); // 橘貓吃不胖 2
person
引用了這個(gè)對(duì)象,通過(guò)person.name
可以獲取到“橘貓吃不胖”的值,通過(guò)person.age
可以獲取到2,那么這時(shí)就可以認(rèn)為“橘貓吃不胖”和2是可達(dá)的。
person = null; console.log(person.name, person.age); // TypeError: Cannot read properties of null (reading 'name')
如果將person
設(shè)置為null
,那么這兩個(gè)值就沒(méi)法獲得了,它們就是不可達(dá)的,這時(shí)JavaScript垃圾回收機(jī)制就會(huì)自動(dòng)從內(nèi)存中將其清除。
那么JavaScript的垃圾回收就是定期找出這些不可達(dá)的對(duì)象,然后將其釋放。那么找出這些不可達(dá)的對(duì)象有兩種常用的策略:
- 標(biāo)記清除法
- 引用計(jì)數(shù)法
2.1 標(biāo)記清除法
標(biāo)記清除法分為標(biāo)記和清除兩個(gè)階段,標(biāo)記階段需要從根節(jié)點(diǎn)遍歷內(nèi)存中的所有對(duì)象,并為可達(dá)的對(duì)象做上標(biāo)記,清除階段則把沒(méi)有標(biāo)記的對(duì)象(非可達(dá)對(duì)象)銷毀。
標(biāo)記清除法的優(yōu)點(diǎn)就是實(shí)現(xiàn)簡(jiǎn)單。
它的缺點(diǎn)有兩個(gè),首先是內(nèi)存碎片化。這是因?yàn)榍謇淼衾?,未被清除的?duì)象內(nèi)存位置是不變的,而被清除掉的內(nèi)存穿插在未被清除的對(duì)象中,導(dǎo)致了內(nèi)存碎片化。
第二個(gè)缺點(diǎn)是內(nèi)存分配速度慢。由于空閑內(nèi)存不是一整塊,假設(shè)新對(duì)象需要的內(nèi)存是size
,那么需要對(duì)空閑內(nèi)存進(jìn)行一次單向遍歷,找出大于等于size
的內(nèi)存才能為其分配。
標(biāo)記清除算法改進(jìn)—— 標(biāo)記整理算法
標(biāo)記清除算法的缺點(diǎn)主要在于內(nèi)存清理之后剩余的內(nèi)存位置不變而導(dǎo)致內(nèi)存碎片化,因此可以使用標(biāo)記整理算法改進(jìn)。
標(biāo)記整理算法的標(biāo)記階段與標(biāo)記清除算法相同,都是從根節(jié)點(diǎn)遍歷內(nèi)存中的所有對(duì)象,為可達(dá)的對(duì)象打上一個(gè)標(biāo)記。但是在標(biāo)記結(jié)束后,標(biāo)記整理算法將這些可達(dá)的對(duì)象移向內(nèi)存的一端,然后清理掉邊界的內(nèi)存。
2.2 引用計(jì)數(shù)法
引用計(jì)數(shù)法主要記錄對(duì)象有沒(méi)有被其他對(duì)象引用,如果沒(méi)有被引用,它將被垃圾回收機(jī)制回收。它的策略是跟蹤記錄每個(gè)變量值被使用的次數(shù),當(dāng)變量值引用次數(shù)為0時(shí),垃圾回收機(jī)制就會(huì)把它清理掉。
示例代碼如下:
let person = { name: "橘貓吃不胖" }; // { name: "橘貓吃不胖" } 引用次數(shù)為1 let person1 = person; // { name: "橘貓吃不胖" } 引用次數(shù)為2 person = null; // { name: "橘貓吃不胖" } 的引用次數(shù)為1 person1 = null; // { name: "橘貓吃不胖" } 的引用次數(shù)為0
引用計(jì)數(shù)法的優(yōu)點(diǎn)是可以實(shí)現(xiàn)立即進(jìn)行垃圾回收。當(dāng)引用計(jì)數(shù)在引用值為0時(shí),立即進(jìn)行垃圾回收,這樣可以達(dá)到立刻垃圾回收的效果。
它的缺點(diǎn)也有兩個(gè),首先它需要一個(gè)計(jì)數(shù)器,這個(gè)計(jì)數(shù)器可能要占據(jù)很大的位置,因?yàn)槲覀儫o(wú)法知道被引用數(shù)量的多少。
第二個(gè)缺點(diǎn)是無(wú)法解決當(dāng)出現(xiàn)循環(huán)引用時(shí)無(wú)法回收的問(wèn)題。例如a
引用了b
,b
也引用了a
,兩個(gè)對(duì)象相互引用,引用計(jì)數(shù)不為0,因此無(wú)法進(jìn)行內(nèi)存清理,如下所示:
let a = { name: "橘貓吃不胖" }; let b = { age: 2 }; a.age = b; b.name = a;
3. V8對(duì)垃圾回收機(jī)制的優(yōu)化——分代式垃圾回收機(jī)制
目前大多數(shù)瀏覽器都是基于標(biāo)記清除算法,V8進(jìn)行了一些優(yōu)化加工處理,采用分代式垃圾回收機(jī)制。
3.1 新生代與老生代
原本的垃圾回收機(jī)制在每次回收時(shí)都要檢查內(nèi)存中所有的對(duì)象,這樣的話,一些大、老、存活時(shí)間長(zhǎng)的對(duì)象與新、小、存活時(shí)間短的對(duì)象檢查頻率相同,但是前者并不需要頻繁進(jìn)行清理,因此采用分代式垃圾回收機(jī)制。
V8中將堆內(nèi)存分為新生代和老生代兩區(qū)域,采用不同的垃圾回收策略進(jìn)行回收。新生代的對(duì)象為存活時(shí)間較短的對(duì)象,通常只支持1~8M的容量,老生代的對(duì)象為存活時(shí)間較長(zhǎng)或常駐內(nèi)存的對(duì)象,容量通常比較大,V8整個(gè)堆內(nèi)存的大小就等于新生代加上老生代的內(nèi)存。
3.2 新生代的垃圾回收
新生代垃圾回收策略中,將堆內(nèi)存一分為二,一個(gè)是處于使用狀態(tài)的使用區(qū),一個(gè)是處于閑置狀態(tài)的空閑區(qū)。
新加入的對(duì)象都會(huì)存放到使用區(qū),當(dāng)使用區(qū)快滿時(shí),就需要執(zhí)行一次垃圾清理操作,即新生代垃圾回收機(jī)制會(huì)對(duì)使用區(qū)中的活動(dòng)對(duì)象(不需要被清理的對(duì)象)做標(biāo)記,標(biāo)記完成之后將這些活動(dòng)對(duì)象復(fù)制到空閑區(qū)并進(jìn)行排序(避免內(nèi)存碎片化),然后將使用區(qū)清空,原來(lái)的空閑區(qū)變?yōu)槭褂脜^(qū),原來(lái)的使用區(qū)變?yōu)榭臻e區(qū)。
當(dāng)一個(gè)對(duì)象經(jīng)過(guò)多次復(fù)制后依然存活,它將會(huì)被認(rèn)為是生命周期較長(zhǎng)的對(duì)象,會(huì)被移動(dòng)到老生代的內(nèi)存中,或者一個(gè)對(duì)象被復(fù)制到空閑區(qū)時(shí),空閑區(qū)占用空間超過(guò)了25%,那么該對(duì)象也會(huì)進(jìn)入老生代內(nèi)存中。
新生代回收策略——并行回收
JavaScript是單線程的語(yǔ)言,當(dāng)執(zhí)行垃圾回收時(shí),就會(huì)阻塞JavaScript腳本的執(zhí)行,垃圾回收結(jié)束后再繼續(xù)JavaScript腳本執(zhí)行,這種情況叫做全停頓(Stop-The-World)。
如果執(zhí)行一次垃圾回收需要100ms,那么腳本執(zhí)行就得暫停100ms,如果執(zhí)行垃圾回收的時(shí)間過(guò)長(zhǎng),那么就會(huì)造成頁(yè)面卡頓,帶來(lái)不好的用戶體驗(yàn)。對(duì)于這樣的情況,可以采用并行回收的策略。
并行回收指的是在主線程進(jìn)行垃圾回收時(shí),同時(shí)開(kāi)啟多個(gè)輔助線程一起執(zhí)行垃圾回收。比如說(shuō)一項(xiàng)任務(wù)一個(gè)人需要30天才能完成,那么如果安排兩個(gè)人甚至多個(gè)人,可能10來(lái)天甚至更短的時(shí)間就完成了。實(shí)現(xiàn)并行回收可以大大降低垃圾回收的暫停時(shí)間。
新生代對(duì)象空間就采用并行策略,在執(zhí)行垃圾回收的過(guò)程中,會(huì)啟動(dòng)了多個(gè)線程來(lái)負(fù)責(zé)新生代中的垃圾清理操作,這些線程同時(shí)將對(duì)象空間中的數(shù)據(jù)移動(dòng)到空閑區(qū)域,這個(gè)過(guò)程中由于數(shù)據(jù)地址會(huì)發(fā)生改變,所以還需要同步更新引用這些對(duì)象的指針,此即并行回收。
3.3 老生代的垃圾回收
老生代的垃圾回收操作主要就是標(biāo)記清除算法的步驟了,在標(biāo)記階段標(biāo)記所有的可達(dá)對(duì)象,清除階段清除掉未被標(biāo)記的對(duì)象。又由于該算法會(huì)出現(xiàn)內(nèi)存碎片的問(wèn)題,因此會(huì)使用標(biāo)記整理算法來(lái)優(yōu)化這個(gè)過(guò)程。
老生代回收策略——增量標(biāo)記與惰性清理 ①增量標(biāo)記
增量就是將一次標(biāo)記的過(guò)程,分成了許多次,每執(zhí)行完一次就讓?xiě)?yīng)用邏輯執(zhí)行一會(huì)兒,這樣交替多次后完成垃圾回收。但是這會(huì)隨之而來(lái)新的問(wèn)題,首先是如何暫停每次標(biāo)記去執(zhí)行JavaScript代碼,還有如果標(biāo)記好的對(duì)象在執(zhí)行js中改變了狀態(tài)成為了可達(dá)或者不可達(dá)對(duì)象怎么辦,V8對(duì)這兩個(gè)問(wèn)題對(duì)應(yīng)的解決方案分別是三色標(biāo)記法與寫(xiě)屏障。
a.三色標(biāo)記法
三色標(biāo)記法使用三種顏色白、灰、黑來(lái)標(biāo)記對(duì)象的狀態(tài)。白色表示初始狀態(tài),黑色表示已檢查狀態(tài),灰色表示待檢查狀態(tài)。
它的過(guò)程為:
1、將所有的對(duì)象設(shè)置為白色,然后從root對(duì)象出發(fā),將所有可以訪問(wèn)的對(duì)象標(biāo)記為灰色,并用一個(gè)數(shù)組緩存起來(lái);
2、遍歷該數(shù)組,每次都把要遍歷的對(duì)象標(biāo)記為黑色并移出,并且把他的相鄰節(jié)點(diǎn)都涂成灰色,并放入隊(duì)列,直到隊(duì)列為空
3、繼續(xù)檢查是否有灰色對(duì)象,如果有繼續(xù)放入隊(duì)列然后循環(huán),直到所有的可訪問(wèn)對(duì)象都變成黑色
采用三色標(biāo)記法后,程序在恢復(fù)執(zhí)行時(shí)可以直接判斷當(dāng)前內(nèi)存中有沒(méi)有灰色節(jié)點(diǎn),如果有灰色節(jié)點(diǎn),那么從灰色節(jié)點(diǎn)開(kāi)始繼續(xù)執(zhí)行,如果沒(méi)有,直接進(jìn)入垃圾清理階段。
b.寫(xiě)屏障
寫(xiě)屏障可以解決第二個(gè)問(wèn)題,如果執(zhí)行任務(wù)程序時(shí)內(nèi)存中標(biāo)記好的對(duì)象引用關(guān)系被修改了,比如說(shuō)黑色對(duì)象引用了白色對(duì)象,那么它就會(huì)將白色對(duì)象改成灰色對(duì)象,這樣就可以保證下一次標(biāo)記時(shí)可以正常進(jìn)行。
②惰性清理
增量標(biāo)記完成后,就開(kāi)始清除垃圾。如果當(dāng)前的可用內(nèi)存可以支持快速的執(zhí)行代碼,就沒(méi)必要立即清理內(nèi)存,而且清理時(shí)沒(méi)必要一次性清理完,可以按需清理。
優(yōu)點(diǎn):大大減少了主線程停頓的時(shí)間,讓用戶與瀏覽器交互的過(guò)程變得更加流暢
缺點(diǎn):并沒(méi)有減少主線程的總暫停的時(shí)間,甚至?xí)晕⒃黾?/p>
老生代回收策略——并發(fā)回收
并發(fā)回收指的是主線程在執(zhí)行JavaScript的過(guò)程中,輔助線程能夠在后臺(tái),完成執(zhí)行垃圾回收的操作,輔助線程在執(zhí)行垃圾回收的時(shí)候,主線程也可以自由執(zhí)行
垃圾回收機(jī)制多次閱讀之后,我受益匪淺,因此寫(xiě)該文章記錄一下~
到此這篇關(guān)于JavaScript垃圾回收機(jī)制原理總結(jié)深入探究的文章就介紹到這了,更多相關(guān)JavaScript垃圾回收內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
面向JavaScript入門(mén)初學(xué)者的二叉搜索樹(shù)算法教程
二叉搜索樹(shù)則是二叉樹(shù)的一種,但它只允許你在左側(cè)節(jié)點(diǎn)儲(chǔ)存比父節(jié)點(diǎn)小的值,右側(cè)只允許儲(chǔ)存比父節(jié)點(diǎn)大的值,這篇文章主要給大家介紹了關(guān)于JavaScript二叉搜索樹(shù)算法的相關(guān)資料,需要的朋友可以參考下2021-09-09關(guān)于驗(yàn)證碼在IE中不刷新的快速解決方法
下面小編就為大家?guī)?lái)一篇關(guān)于驗(yàn)證碼在IE中不刷新的快速解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09JS實(shí)現(xiàn)帶有抽屜效果的產(chǎn)品類網(wǎng)站多級(jí)導(dǎo)航菜單代碼
這篇文章主要介紹了JS實(shí)現(xiàn)帶有抽屜效果的產(chǎn)品類網(wǎng)站多級(jí)導(dǎo)航菜單代碼,涉及JavaScript動(dòng)態(tài)操作頁(yè)面元素屬性的技巧,整體界面效果美觀大方,具有極強(qiáng)的立體感,需要的朋友可以參考下2015-09-09ES6中常見(jiàn)基本知識(shí)點(diǎn)的基本使用實(shí)例匯總
這篇文章主要給大家介紹了關(guān)于ES6中常見(jiàn)基本知識(shí)點(diǎn)的基本使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-04-04js調(diào)用activeX獲取u盤(pán)序列號(hào)的代碼
js調(diào)用activeX獲取u盤(pán)序列號(hào)的代碼,需要的朋友可以參考下。2011-11-11