一文詳解Javascript內(nèi)存機制與垃圾回收
一 內(nèi)存機制
1.1 數(shù)據(jù)類型
Javascript 是一種動態(tài)的(在運行過程中檢查數(shù)據(jù)類型,)、弱類型(同一個變量可以保存不同類型的數(shù)據(jù))的語言。
Javascript 數(shù)據(jù)類型一共有 8 種: Boolean,Null,Undefined,Number,BigInt,String,Symbol,Object。前 7 種數(shù)據(jù)類型為原始類型,Object為引用類型。兩種類型的數(shù)據(jù)在內(nèi)存中存放的位置不同。
1.2 內(nèi)存空間
Javascript 在執(zhí)行的過程中,主要有三種類型的內(nèi)存空間,分別是:代碼空間、??臻g、堆空間。
代碼空間主要是存儲可執(zhí)行代碼的。
原始數(shù)據(jù)類型存儲在??臻g中, 引用數(shù)據(jù)類型存儲在堆空間中。
說明示例:
function foo(){
var a = "極客時間"
var b = a
var c = {name:"極客時間"}
var d = c
}
foo()??臻g,也就是之前提起的調(diào)用棧,用來存儲執(zhí)行上下文。
上述代碼執(zhí)行到第 3 行的時候,變量 a 和 b 的值都直接保存在執(zhí)行上下文中,執(zhí)行上下文又被壓入??臻g中,所以可以理解為變量 a 和 b 都存放在??臻g中。

當(dāng)執(zhí)行到第 4 行,Javascript 引擎判斷變量 c 的值是引用類型,Javascript 引擎將該值分配到堆空間里,分配后該值會有一個在堆空間的地址,然后將該地址賦值給變量 c。第 5 行,將 c 賦值給 d,實際是將引用地址賦值給了 d,修改引用類型對象的值,改的是堆空間中數(shù)據(jù)的值。

??臻g因為要維護執(zhí)行上下文,影響程序的執(zhí)行效率,所以空間一般比較小,可以用來存放原始類型的小數(shù)據(jù);引用類型的數(shù)據(jù)可以很大,所以堆空間比較大,不過堆空間分配內(nèi)存和回收內(nèi)存會占用一定的時間。
??臻g切換執(zhí)行上下文狀態(tài):

1.3 閉包內(nèi)的數(shù)據(jù)存儲
閉包內(nèi)的變量存儲到??臻g還是堆空間?
說明示例:
function foo() {
var myName = "極客時間"
let test1 = 1
const test2 = 2
var innerBar = {
setName:function(newName){
myName = newName
},
getName:function(){
console.log(test1)
return myName
}
}
return innerBar
}
var bar = foo()
bar.setName("極客邦")
bar.getName()
console.log(bar.getName())執(zhí)行上述代碼,當(dāng) foo 函數(shù)執(zhí)行完之后,調(diào)用棧中的 foo 函數(shù)的執(zhí)行上下文會被銷毀,由于變量 myName 和 test1 持續(xù)存在外部引用,Javascript 引擎判斷這是一個閉包,會在堆空間中創(chuàng)建一個closure(foo)的對象,這個對象中包含這兩個被引用的變量。

二 垃圾回收
一些數(shù)據(jù)在使用后就不再需要了,這部分數(shù)據(jù)繼續(xù)存在內(nèi)存里,并且逐漸積累,會占用越來越多的空間,通過垃圾回收機制以釋放有限的內(nèi)存空間。
2.1 不同語言的垃圾回收策略
一般,垃圾回收分為手動回收和自動回收兩種策略。
以 C / C++ 為例,使用的是手動回收策略,內(nèi)存的分配與銷毀都是由程序員寫的代碼控制的,如果不主動銷毀垃圾數(shù)據(jù),將導(dǎo)致內(nèi)存泄漏。
以 Javascript / Java 為例,垃圾數(shù)據(jù)由內(nèi)置的垃圾回收器自動回收,不需要通過編寫代碼手動釋放。
2.2 調(diào)用棧的垃圾回收機制
Javascript 引擎把執(zhí)行上下文壓入調(diào)用棧的同時,使用一個記錄當(dāng)前執(zhí)行狀態(tài)的指針(ESP)指向當(dāng)前的執(zhí)行上下文,表示正在執(zhí)行該執(zhí)行上下文。
當(dāng)該執(zhí)行上下文運行完畢,ESP 下移,這個下移的操作就是銷毀被執(zhí)行過的上下文的過程。
示例說明:
function foo(){
var a = 1
var b = {name:"極客邦"}
function showName(){
var c = 2
var d = {name:"極客時間"}
}
showName()
}
foo()
2.3 堆空間垃圾回收機制
2.3.1 代際假說
代際假說觀點認為,大部分對象在內(nèi)存中有用的時間很短,一經(jīng)分配內(nèi)存,很快就變得不可訪問;少量數(shù)據(jù)持續(xù)活躍,活的很久。
2.3.2 V8 垃圾回收機制:分代收集
基于代際假說,V8 把堆分為老生代和新生代,新生代中存放的是生存時間短的對象(1—8M空間),老生代中存放的是生存時間久的對象(空間比較大)。
針對新生代和老生代,V8 使用不用的回收機制,以便更高效的進行垃圾回收。使用主垃圾回收器回收老生代的垃圾數(shù)據(jù),使用副垃圾回收器回收新生代的垃圾數(shù)據(jù)。
垃圾回收原理
- 標(biāo)記空間中的活動對象(還在使用的對象)和非活動對象(可以回收的對象)。
- 標(biāo)記完成之后,統(tǒng)一清理內(nèi)存中所有被標(biāo)記的可回收對象,回收這部分對象占據(jù)的內(nèi)存。
- 內(nèi)存整理:頻繁回收對象之后,內(nèi)存中存在大量不連續(xù)空間(內(nèi)存碎片),如果要分配較大連續(xù)內(nèi)存時,可能出現(xiàn)內(nèi)存不足的情況,所以需要將內(nèi)存碎片成連續(xù)的空間。
副垃圾回收器
主要負責(zé)新生代的垃圾回收。新生代空間較小,里面的對象一般較小;回收頻繁。
回收機制采用 Scavenge 算法。該算法把新生代空間劃半分為對象區(qū)域和空閑區(qū)域。

新加入的對象存到對象區(qū)域,當(dāng)對象區(qū)域快被寫滿時,執(zhí)行一次垃圾回收。在回收過程中,對對象區(qū)域里的對象做標(biāo)記,標(biāo)記完成后,把存活的對象復(fù)制到空閑區(qū)域中并有序的排列起來(消除內(nèi)存碎片),把無用的對象清理掉。
復(fù)制完成后,對象區(qū)域和空閑區(qū)域進行角色翻轉(zhuǎn),對象區(qū)域變成空閑區(qū)域,空閑區(qū)域變成對象區(qū)域。這樣這兩塊區(qū)域可以無限重復(fù)使用下去。
復(fù)制操作需要時間成本,因此為了執(zhí)行效率,新生區(qū)的空間一般設(shè)置的比較小。
經(jīng)過兩次垃圾回收依然存活的對象,會被移動到老生區(qū)中,以防止過多的存活對象擠滿新生區(qū)。
主垃圾回收器
主要負責(zé)老生區(qū)的垃圾回收。老生區(qū)的里的對象一般占用空間大(因復(fù)制操作耗時所以不適合使用 Scavenge 算法),存活的時間比較長。
回收機制采用的是 標(biāo)記-清除(Mark-Sweep)算法。從一組根元素開始,遞歸遍歷這組根元素,在遍歷過程中能到達的元素稱為活動對象,沒有達到的元素判斷標(biāo)記為垃圾數(shù)據(jù)。

標(biāo)記完成之后,執(zhí)行清除過程。

如上圖,清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片;為了避免內(nèi)存碎片,進化出了另一種算法:標(biāo)記-整理(Mark-Compact),標(biāo)記完成后,讓所有存活的對象移向內(nèi)存一端,然后直接清理掉端邊界外的數(shù)據(jù)。
全停頓
Javascript 是運行在主線程上的(單線程),一旦執(zhí)行垃圾回收算法,其他 Javascript 腳本將被阻塞,需要等待垃圾回收完畢才能繼續(xù)執(zhí)行。這種行為叫做全停頓。

如上圖,過長的全停頓將導(dǎo)致頁面卡頓。新生代因其空間小存活對象占用空間小,對全停頓影響不大,老生代的垃圾回收是造成全停頓的主因。為了降低老生代垃圾回收造成的卡頓,V8 將標(biāo)記過程分為一個個子標(biāo)記,讓子標(biāo)記和 Javascript 腳本交替進行,這個解決方案叫做增量標(biāo)記算法。

以上就是一文詳解Javascript內(nèi)存機制與垃圾回收的詳細內(nèi)容,更多關(guān)于Javascript內(nèi)存機制與垃圾回收的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript?中如何攔截全局?Fetch?API?的請求和響應(yīng)問題
在本文中,我們介紹了什么是 JavaScript 攔截器,學(xué)習(xí)了如何通過給 Fetch API 使用猴子補丁和使用 fetch-intercept 庫來創(chuàng)建攔截器,對js攔截全局Fetch?API的請求和響應(yīng)知識感興趣的朋友跟隨小編一起看看吧2023-01-01
JavaScript將字符轉(zhuǎn)換為ASCII碼的實現(xiàn)方法
在Web前端開發(fā)中,字符編碼是處理文本數(shù)據(jù)時不可或缺的一部分,ASCII是一種廣泛使用的字符編碼標(biāo)準(zhǔn),它為每個字符分配了一個唯一的數(shù)字表示,解如何將字符轉(zhuǎn)換為ASCII碼,對于解析、生成和操作字符串具有重要意義,本文將詳細介紹這一過程,并提供多個實用的代碼示例2024-12-12
TypeScript中實現(xiàn)字符串格式化及數(shù)值對齊
字符串格式化是處理文本展示的常見需求,尤其在需要規(guī)范數(shù)值、日期等數(shù)據(jù)的顯示格式時非常重要,TypeScript可以通過模板字符串、字符串方法和正則表達式組合實現(xiàn)類似功能,包括數(shù)值的左右對齊等場景,本文給大家介紹的非常詳細,需要的朋友可以參考下2025-08-08
javascript二維數(shù)組轉(zhuǎn)置實例
這篇文章主要介紹了javascript二維數(shù)組轉(zhuǎn)置方法,實例分析了數(shù)組行列交換的轉(zhuǎn)置技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01
JavaScript常用數(shù)學(xué)函數(shù)用法示例
這篇文章主要介紹了JavaScript常用數(shù)學(xué)函數(shù)用法,結(jié)合實例形式分析了JavaScript常見的對數(shù)、平方、絕對值、正弦、四舍五入等相關(guān)數(shù)學(xué)函數(shù)使用技巧,需要的朋友可以參考下2018-05-05

