js引擎垃圾回收機制示例詳解
內存管理機制
在計算機語言中,內存管理機制一般分為以下幾種:
- 手動管理
手動管理以C、C++為代表,對象分配內存后,需要程序員手動調用釋放內存的代碼。這種方式的效率是最高的。
- 自動管理
目前自動內存管理比較主流,如java、js、python等,我們在寫代碼的時候基本不用關心內存管理問題,內存的分配以及垃圾內存的回收都會由系統(tǒng)自動完成,我們稱這種方式為GC。這種方式對于我們寫代碼來說非常方便,讓我們的注意力集中在業(yè)務代碼的實現(xiàn)上,而不用過多關注內存問題。
- 半自動管理
半自動管理以蘋果的OC、Swift為例,主要使用的是引用計數(shù)來管理內存。之所以我稱它為半自動管理,是因為我們關注的是它的引用計數(shù)。拿OC為說,alloc、copy、retain等關鍵字會讓對象引用計數(shù)加1,release會讓引用計數(shù)減1,當對象引用計數(shù)為0時,這片內存便會被系統(tǒng)回收。所以在早期的iOS開發(fā)中,代碼中經(jīng)常會出現(xiàn)[xxx release]的代碼,這就是我們所說的MRC。與MRC相對應的是ARC,ARC不需要我們手動的調用release代碼,系統(tǒng)會根據(jù)代碼上下文自動的為我們添加release,也就是自動幫我們管理對象的引用計數(shù)。
V8引擎的內存回收機制
了解了以上內存管理機制,我們知道js的內存管理使用的是自動內存管理,本篇文章我們就來詳細講一下js的垃圾回收機制。
js的垃圾回收是由瀏覽器引擎來做的,不同瀏覽器的垃圾回收機制在細節(jié)上略有不同,但回收算法大體上是通用的。我們以Chrome的V8引擎為例進行說明。
js內存分為棧內存和堆內存,在js中引用類型是存儲在堆上的;棧內存中主要存儲的是占用內存較小的非引用類型,以及引用類型引用地址。
var a = "123"
var b = 123
var c = {name:"123"}如以上代碼在堆棧中的存儲結構:

棧內存回收:
說棧內存回收之前,我們要先說一下js函數(shù)是怎么調用的,當我們調用一個函數(shù)時,在??臻g內會形成一個函數(shù)的上下文。上下文中包含了函數(shù)中的變量環(huán)境和詞法環(huán)境,其中var變量和function變量存儲在變量環(huán)境中,let和const變量存儲在詞法環(huán)境中。
var a = "123"
function func1() {
var b = "123"
console.log(b)
func2()
}
funcgion func2() {
const c = "456"
console.log(c)
}
func1()上面代碼在棧內存中的狀態(tài):

除了執(zhí)行上下文外,同時在棧中還有一個ESP指針記錄著當前代碼的執(zhí)行狀態(tài),上圖的ESP指針指向了func2,代表當前執(zhí)行到了func2。當func2指行完成之后,ESP指針就會移動到func1,同時func2中的非引用類型的變量內存將會被收回。
堆內存的回收
棧內存中ESP指針移動后,函數(shù)的執(zhí)行上下文出棧,那么對應的非引用類型的內存以及引用類型的引用被回收了,但是引用類型在堆中所占用的內存并沒有被回收。那接下來我們來看一下V8引擎怎么處理堆中的垃圾回收的。
V8引擎將堆內存分為新生代和老生代兩個區(qū)域,新生代一般較小,存儲的是新創(chuàng)建的對象,老生代是指存活時間比較久或者說活動的對象。V8引擎為了提升回收性能,新生代和老生代使用了不同的回收策略和兩個垃圾回收器,副垃圾回收器用來回收新生代區(qū)域的垃圾內存,主垃圾回收器用來回收老生代的垃圾內存。
- 新生代垃圾回收
新生代的垃圾回收由副垃圾回收器負責,新生代區(qū)域又被分為兩個區(qū)域,一半為使用區(qū),一半為空閑區(qū)。如圖:

一般新的對象會被分配在使用區(qū),當使用區(qū)內存即將占滿時垃圾回收器會進行一次垃圾回收。副垃圾回收器主要使用的是標記-清除(Mark-Sweep)算法。 回收過程中大概分為以下幾個步驟
區(qū)分活動對象和非活動對象,并對活動對象進行標記。(活動對象就是指還在使用的對象,非活動對象就是需要清理的對象)
標記完成之后將使用區(qū)的活動對象復制進空閑區(qū),并進行內存整理排序,以避免產生內存碎片。
清理使用區(qū)的非活動對象,釋放垃圾內存。
把使用區(qū)和活動區(qū)進行互換,以達到內存清理和整理的目的,當新的使用區(qū)即將被占滿時會執(zhí)行一次新的內存清理。
但是由于新生代的區(qū)域不是很大,區(qū)域很容易被占滿,所以當對象經(jīng)過兩次垃圾回收依然沒有被清理時,將會被移動到老生代區(qū)域,這種策略我們稱之為“對象晉升”。
- 老生代垃圾回收
老生代區(qū)域使用的主垃圾回收器,老生代中一般存放的是存活時間比較久以及占用內存比較多的對象,所以老生代的內存比較大。由于復制大量內存需要占用時間比較久,所以老生你無法像新生你那樣進行區(qū)域交換。
主垃圾回收器使用的除了上面所說的標記-清除(Mark-Sweep)算法外,還有一個標記-整理(Mark-Compact)算法,主垃圾回收器的回收過程是這樣的:
標記階段就是從一組根元素開始,遞歸遍歷這組根元素,在這個遍歷過程中,能到達的元素稱為活動對象,沒有到達的元素就可以判斷為垃圾數(shù)據(jù)(這一點跟新生代的標記是一致的)
清理階段就是清理掉垃圾數(shù)據(jù)
由于標記清除對象后,內存會產生內存碎片,會導致大對象無法分配內存,從而造成內存不足。所以標記整理算法此時就排上了用場,標記整理算法會將活動對象向一端移動排序,從而避免產生內存碎片。
并行、并發(fā)與小任務回收
由于js運行在主線程,如果在執(zhí)行垃圾回收操作全部放在主線程,再加上老生代區(qū)域內存較大,垃圾回收執(zhí)行的時間可能會比較長,那么主線程的js任務就必須處于一個等待狀態(tài),從而造成頁面卡頓,這種方式我們稱之為全停頓。因此V8引擎引入了并行回收策略和子任務增量標記策略。
- 并行回收
并行回收,即:在主線程之外,開除幾條輔助線程并行執(zhí)行垃圾回收任務。這樣就可以大大減少垃圾回收的時間,從而解決js阻塞問題。
- 子任務回收(增量標記) V8引擎將一次完整的垃圾回收任務分成多個小的子任務,與JS交替執(zhí)行。這種方式雖然并沒有縮短垃圾回收執(zhí)行的時間,由于每個子任務很小,執(zhí)行時間很短,給了線程速響應js任務的機會,從頁避免了出現(xiàn)卡頓,由于它的標記任務是增量進行的,所以我們又稱之為增量標記。如圖:

- 并發(fā)回收
并發(fā)回收是與并行回收類似,都是開啟輔助線程執(zhí)行GC任務。不同的是,并發(fā)回收機制的GC任務全部交由輔助線程來完成,主線程可以隨時響應js任務,而不需要被間歇的掛起來完成GC任務。

總結
到此這篇關于js引擎垃圾回收機制的文章就介紹到這了,更多相關js引擎垃圾回收機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
js RuntimeObject() 獲取ie里面自定義函數(shù)或者屬性的集合
取得ie 里面 自定義函數(shù)或者屬性的集合 使用RuntimeObject()實現(xiàn),需要的朋友可以參考下。2010-11-11
微信小程序使用uni-app實現(xiàn)首頁搜索框導航欄功能詳解
這篇文章主要介紹了微信小程序使用uni-app實現(xiàn)首頁搜索框導航欄功能,uni-app?是一個使用?Vue.js?(opens?new?window)開發(fā)所有前端應用的框架,開發(fā)者編寫一套代碼,可發(fā)布到iOS、Android、Web(響應式)、以及各種小程序2022-10-10
JavaScript最完整的深淺拷貝實現(xiàn)方式詳解
這篇文章主要為大家詳細介紹了JavaScript最完整的深淺拷貝實現(xiàn)方式,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02

