js引擎垃圾回收機(jī)制示例詳解
內(nèi)存管理機(jī)制
在計(jì)算機(jī)語(yǔ)言中,內(nèi)存管理機(jī)制一般分為以下幾種:
- 手動(dòng)管理
手動(dòng)管理以C
、C++
為代表,對(duì)象分配內(nèi)存后,需要程序員手動(dòng)調(diào)用釋放內(nèi)存的代碼。這種方式的效率是最高的。
- 自動(dòng)管理
目前自動(dòng)內(nèi)存管理比較主流,如java
、js
、python
等,我們?cè)趯?xiě)代碼的時(shí)候基本不用關(guān)心內(nèi)存管理問(wèn)題,內(nèi)存的分配以及垃圾內(nèi)存的回收都會(huì)由系統(tǒng)自動(dòng)完成,我們稱(chēng)這種方式為GC
。這種方式對(duì)于我們寫(xiě)代碼來(lái)說(shuō)非常方便,讓我們的注意力集中在業(yè)務(wù)代碼的實(shí)現(xiàn)上,而不用過(guò)多關(guān)注內(nèi)存問(wèn)題。
- 半自動(dòng)管理
半自動(dòng)管理以蘋(píng)果的OC
、Swift
為例,主要使用的是引用計(jì)數(shù)來(lái)管理內(nèi)存。之所以我稱(chēng)它為半自動(dòng)管理,是因?yàn)槲覀冴P(guān)注的是它的引用計(jì)數(shù)。拿OC
為說(shuō),alloc
、copy
、retain
等關(guān)鍵字會(huì)讓對(duì)象引用計(jì)數(shù)加1,release
會(huì)讓引用計(jì)數(shù)減1,當(dāng)對(duì)象引用計(jì)數(shù)為0時(shí),這片內(nèi)存便會(huì)被系統(tǒng)回收。所以在早期的iOS開(kāi)發(fā)中,代碼中經(jīng)常會(huì)出現(xiàn)[xxx release]
的代碼,這就是我們所說(shuō)的MRC
。與MRC
相對(duì)應(yīng)的是ARC
,ARC
不需要我們手動(dòng)的調(diào)用release
代碼,系統(tǒng)會(huì)根據(jù)代碼上下文自動(dòng)的為我們添加release
,也就是自動(dòng)幫我們管理對(duì)象的引用計(jì)數(shù)。
V8引擎的內(nèi)存回收機(jī)制
了解了以上內(nèi)存管理機(jī)制,我們知道js
的內(nèi)存管理使用的是自動(dòng)內(nèi)存管理,本篇文章我們就來(lái)詳細(xì)講一下js
的垃圾回收機(jī)制。
js
的垃圾回收是由瀏覽器引擎來(lái)做的,不同瀏覽器的垃圾回收機(jī)制在細(xì)節(jié)上略有不同,但回收算法大體上是通用的。我們以Chrome
的V8
引擎為例進(jìn)行說(shuō)明。
js
內(nèi)存分為棧內(nèi)存和堆內(nèi)存,在js
中引用類(lèi)型是存儲(chǔ)在堆上的;棧內(nèi)存中主要存儲(chǔ)的是占用內(nèi)存較小的非引用類(lèi)型,以及引用類(lèi)型引用地址。
var a = "123" var b = 123 var c = {name:"123"}
如以上代碼在堆棧中的存儲(chǔ)結(jié)構(gòu):
棧內(nèi)存回收:
說(shuō)棧內(nèi)存回收之前,我們要先說(shuō)一下js函數(shù)是怎么調(diào)用的,當(dāng)我們調(diào)用一個(gè)函數(shù)時(shí),在棧空間內(nèi)會(huì)形成一個(gè)函數(shù)的上下文。上下文中包含了函數(shù)中的變量環(huán)境和詞法環(huán)境,其中var變量和function變量存儲(chǔ)在變量環(huán)境中,let和const變量存儲(chǔ)在詞法環(huán)境中。
var a = "123" function func1() { var b = "123" console.log(b) func2() } funcgion func2() { const c = "456" console.log(c) } func1()
上面代碼在棧內(nèi)存中的狀態(tài):
除了執(zhí)行上下文外,同時(shí)在棧中還有一個(gè)ESP
指針記錄著當(dāng)前代碼的執(zhí)行狀態(tài),上圖的ESP
指針指向了func2
,代表當(dāng)前執(zhí)行到了func2
。當(dāng)func2
指行完成之后,ESP
指針就會(huì)移動(dòng)到func1
,同時(shí)func2
中的非引用類(lèi)型的變量?jī)?nèi)存將會(huì)被收回。
堆內(nèi)存的回收
棧內(nèi)存中ESP
指針移動(dòng)后,函數(shù)的執(zhí)行上下文出棧,那么對(duì)應(yīng)的非引用類(lèi)型的內(nèi)存以及引用類(lèi)型的引用被回收了,但是引用類(lèi)型在堆中所占用的內(nèi)存并沒(méi)有被回收。那接下來(lái)我們來(lái)看一下V8
引擎怎么處理堆中的垃圾回收的。
V8引擎將堆內(nèi)存分為新生代和老生代兩個(gè)區(qū)域,新生代一般較小,存儲(chǔ)的是新創(chuàng)建的對(duì)象,老生代是指存活時(shí)間比較久或者說(shuō)活動(dòng)的對(duì)象。V8引擎為了提升回收性能,新生代和老生代使用了不同的回收策略和兩個(gè)垃圾回收器,副垃圾回收器用來(lái)回收新生代區(qū)域的垃圾內(nèi)存,主垃圾回收器用來(lái)回收老生代的垃圾內(nèi)存。
- 新生代垃圾回收
新生代的垃圾回收由副垃圾回收器負(fù)責(zé),新生代區(qū)域又被分為兩個(gè)區(qū)域,一半為使用區(qū),一半為空閑區(qū)。如圖:
一般新的對(duì)象會(huì)被分配在使用區(qū),當(dāng)使用區(qū)內(nèi)存即將占滿(mǎn)時(shí)垃圾回收器會(huì)進(jìn)行一次垃圾回收。副垃圾回收器主要使用的是標(biāo)記-清除(Mark-Sweep)算法。 回收過(guò)程中大概分為以下幾個(gè)步驟
區(qū)分活動(dòng)對(duì)象和非活動(dòng)對(duì)象,并對(duì)活動(dòng)對(duì)象進(jìn)行標(biāo)記。(活動(dòng)對(duì)象就是指還在使用的對(duì)象,非活動(dòng)對(duì)象就是需要清理的對(duì)象)
標(biāo)記完成之后將使用區(qū)的活動(dòng)對(duì)象復(fù)制進(jìn)空閑區(qū),并進(jìn)行內(nèi)存整理排序,以避免產(chǎn)生內(nèi)存碎片。
清理使用區(qū)的非活動(dòng)對(duì)象,釋放垃圾內(nèi)存。
把使用區(qū)和活動(dòng)區(qū)進(jìn)行互換,以達(dá)到內(nèi)存清理和整理的目的,當(dāng)新的使用區(qū)即將被占滿(mǎn)時(shí)會(huì)執(zhí)行一次新的內(nèi)存清理。
但是由于新生代的區(qū)域不是很大,區(qū)域很容易被占滿(mǎn),所以當(dāng)對(duì)象經(jīng)過(guò)兩次垃圾回收依然沒(méi)有被清理時(shí),將會(huì)被移動(dòng)到老生代區(qū)域,這種策略我們稱(chēng)之為“對(duì)象晉升”。
- 老生代垃圾回收
老生代區(qū)域使用的主垃圾回收器,老生代中一般存放的是存活時(shí)間比較久以及占用內(nèi)存比較多的對(duì)象,所以老生代的內(nèi)存比較大。由于復(fù)制大量?jī)?nèi)存需要占用時(shí)間比較久,所以老生你無(wú)法像新生你那樣進(jìn)行區(qū)域交換。
主垃圾回收器使用的除了上面所說(shuō)的標(biāo)記-清除(Mark-Sweep)算法外,還有一個(gè)標(biāo)記-整理(Mark-Compact)算法,主垃圾回收器的回收過(guò)程是這樣的:
標(biāo)記階段就是從一組根元素開(kāi)始,遞歸遍歷這組根元素,在這個(gè)遍歷過(guò)程中,能到達(dá)的元素稱(chēng)為活動(dòng)對(duì)象,沒(méi)有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)(這一點(diǎn)跟新生代的標(biāo)記是一致的)
清理階段就是清理掉垃圾數(shù)據(jù)
由于標(biāo)記清除對(duì)象后,內(nèi)存會(huì)產(chǎn)生內(nèi)存碎片,會(huì)導(dǎo)致大對(duì)象無(wú)法分配內(nèi)存,從而造成內(nèi)存不足。所以標(biāo)記整理算法此時(shí)就排上了用場(chǎng),標(biāo)記整理算法會(huì)將活動(dòng)對(duì)象向一端移動(dòng)排序,從而避免產(chǎn)生內(nèi)存碎片。
并行、并發(fā)與小任務(wù)回收
由于js運(yùn)行在主線(xiàn)程,如果在執(zhí)行垃圾回收操作全部放在主線(xiàn)程,再加上老生代區(qū)域內(nèi)存較大,垃圾回收?qǐng)?zhí)行的時(shí)間可能會(huì)比較長(zhǎng),那么主線(xiàn)程的js任務(wù)就必須處于一個(gè)等待狀態(tài),從而造成頁(yè)面卡頓,這種方式我們稱(chēng)之為全停頓。因此V8引擎引入了并行回收策略和子任務(wù)增量標(biāo)記策略。
- 并行回收
并行回收,即:在主線(xiàn)程之外,開(kāi)除幾條輔助線(xiàn)程并行執(zhí)行垃圾回收任務(wù)。這樣就可以大大減少垃圾回收的時(shí)間,從而解決js阻塞問(wèn)題。
- 子任務(wù)回收(增量標(biāo)記) V8引擎將一次完整的垃圾回收任務(wù)分成多個(gè)小的子任務(wù),與JS交替執(zhí)行。這種方式雖然并沒(méi)有縮短垃圾回收?qǐng)?zhí)行的時(shí)間,由于每個(gè)子任務(wù)很小,執(zhí)行時(shí)間很短,給了線(xiàn)程速響應(yīng)js任務(wù)的機(jī)會(huì),從頁(yè)避免了出現(xiàn)卡頓,由于它的標(biāo)記任務(wù)是增量進(jìn)行的,所以我們又稱(chēng)之為增量標(biāo)記。如圖:
- 并發(fā)回收
并發(fā)回收是與并行回收類(lèi)似,都是開(kāi)啟輔助線(xiàn)程執(zhí)行GC任務(wù)。不同的是,并發(fā)回收機(jī)制的GC任務(wù)全部交由輔助線(xiàn)程來(lái)完成,主線(xiàn)程可以隨時(shí)響應(yīng)js任務(wù),而不需要被間歇的掛起來(lái)完成GC任務(wù)。
總結(jié)
到此這篇關(guān)于js引擎垃圾回收機(jī)制的文章就介紹到這了,更多相關(guān)js引擎垃圾回收機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript關(guān)鍵字this的使用方法詳解
與其他語(yǔ)言相比,函數(shù)的 this 關(guān)鍵字在 JavaScript 中的表現(xiàn)略有不同,此外,在嚴(yán)格模式和非嚴(yán)格模式之間也會(huì)有一些差別,本文就給大家講解一下JavaScript關(guān)鍵字中的this,需要的朋友可以參考下2023-08-08JavaScript簡(jiǎn)單遍歷DOM對(duì)象所有屬性的實(shí)現(xiàn)方法
這篇文章主要介紹了JavaScript簡(jiǎn)單遍歷DOM對(duì)象所有屬性的實(shí)現(xiàn)方法,涉及JavaScript針對(duì)頁(yè)面元素屬性操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10javascript showModalDialog 內(nèi)跳轉(zhuǎn)頁(yè)面的問(wèn)題
在頁(yè)面中使用了showModalDialog,但是在跳轉(zhuǎn)鏈接時(shí),不會(huì)在當(dāng)前頁(yè)執(zhí)行,而是彈出一個(gè)新的頁(yè)面。2010-11-11HTML使用js給input標(biāo)簽增加disabled屬性的方法
最近項(xiàng)目上提出一個(gè)經(jīng)常遇到的需求,點(diǎn)擊新增時(shí)input可輸入,點(diǎn)擊編輯時(shí)input置灰,下面這篇文章主要給大家介紹了關(guān)于HTML使用js給input標(biāo)簽增加disabled屬性的相關(guān)資料,需要的朋友可以參考下2024-06-06簡(jiǎn)單實(shí)現(xiàn)js無(wú)縫滾動(dòng)效果
這篇文章主要教大家如何簡(jiǎn)單實(shí)現(xiàn)js無(wú)縫滾動(dòng)效果,js輪播圖實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02詳解JavaScript數(shù)組過(guò)濾相同元素的5種方法
本篇文章主要介紹了詳解JavaScript數(shù)組過(guò)濾相同元素的5種方法,詳細(xì)的介紹了5種實(shí)用方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05javascript調(diào)試過(guò)程中找不到哪里出錯(cuò)的可能原因
本文為大家講解下在寫(xiě)javascript時(shí)找不到哪里出錯(cuò)的可能原因,遇到的朋友可以參考下2013-12-12360doc網(wǎng)站不登錄就無(wú)法復(fù)制內(nèi)容的解決方法
這篇文章主要介紹了360doc網(wǎng)站不登錄就無(wú)法復(fù)制內(nèi)容的解決方法,需要的朋友可以參考下2018-01-01