JS 的垃圾回收機制

前言

垃圾回收(Garbage Collection)是一種內(nèi)存管理機制伦吠,用于檢測和清理不再被程序使用的內(nèi)存。垃圾回收器會在 JS 引擎(瀏覽器或者 nodejs)內(nèi)部周期性地運行,開發(fā)者無需手動操作镇眷。

但是吧史,了解垃圾回收機制的工作原理有助于我們寫出更加高效的 JS 代碼,使 JS 引擎更好的幫助我們完成垃圾回收蕴轨,避免我們開發(fā)的應用出現(xiàn)內(nèi)存泄漏問題港谊。

垃圾是怎樣產(chǎn)生的?

JS 中的數(shù)據(jù)類型有原始類型和引用類型橙弱,原始類型占用的內(nèi)存極小歧寺,一般是字符串燥狰、數(shù)字、布爾值這些斜筐,他們被存放在棧(stack)中龙致。引用類型可以是數(shù)組、普通對象或者函數(shù)顷链,他們一般會包含較多的數(shù)據(jù)目代,所以引用類型的實際數(shù)據(jù)存放在內(nèi)存的堆(heap)中,然后在棧中會存放一個指向該實際數(shù)據(jù)的地址蕴潦。

const str = "abc" // 原始類型
const obj = { foo: "bar" } // 引用類型

在 JS 中每聲明一個變量像啼,該應用所占的內(nèi)存就會相應的增加,但機器的內(nèi)存是有限的潭苞,內(nèi)存占用不能無限制的增加忽冻。那些不再被程序使用的數(shù)據(jù),就被稱為垃圾此疹,他們所占用的內(nèi)存就會被 JS 引擎的垃圾回收機制回收僧诚。

垃圾回收機制會回收哪些垃圾?

當一個對象不在任何地方被引用時(如何判斷是否被引用由算法決定蝗碎,后面會介紹算法)湖笨,垃圾回收器就需要將這些對象進行標記,并在適當?shù)臅r機回收它們的內(nèi)存蹦骑。

以下面這段代碼為例:

function myFunction() {
  const obj = { foo: "bar" }
  // do something
}

myFunction()

這段代碼中的 obj 對象在 myFunction 函數(shù)執(zhí)行之后慈省,就已經(jīng)沒有被引用了,就需要被回收眠菇。

但當某個對象從開發(fā)角度上來說不再被使用了边败,卻意外的仍然在某個地方被引用,垃圾回收器就無法回收它的內(nèi)存捎废,就會造成內(nèi)存泄漏(內(nèi)存逐漸累積笑窜,程序占用的內(nèi)存越來越多,當超過系統(tǒng)的可用內(nèi)存時登疗,就會造成程序崩潰)排截。

比如下面這段代碼中 obj 對象就有可能不會被回收(取決于算法):

function myFunction() {
  const obj = { foo: "bar" }

  setTimeout(() => {
    console.log(obj.foo)
  }, 1000)
}

myFunction()

因為 obj 作為閉包中的引用傳遞給了定時器的回調(diào)函數(shù),即使 myFunction 執(zhí)行完畢辐益,由于定時器沒有被清除断傲,obj 仍然被定時器回調(diào)函數(shù)持有引用,就可能導致 obj 不會被垃圾回收智政。

垃圾回收的算法

目前 JavaScript 中的垃圾回收機制主要基于以下兩種算法:

引用計數(shù)(Reference-Counting)算法

該算法的原理是艳悔,記錄每個對象的引用次數(shù),引用增加時計數(shù)器加一女仰,引用減少時減一猜年,當引用計數(shù)變?yōu)榱銜r抡锈,就表示沒有任何引用指向該對象了,該對象就可以被回收乔外。

這種規(guī)則導致該算法有一個缺點:無法回收循環(huán)引用的對象床三,因為互相引用的對象在程序結(jié)束時都至少有一次引用。比如下面這段代碼:

function myFunction() {
  const a = {}
  const b = {}
  a.b = b // a 引用了 b
  b.a = a // b 引用了 a
}

myFunction()

在該算法下杨幼,即使在 myFunction 執(zhí)行之后撇簿,對象 ab 也不會被回收。

IE 6 和 IE 7 使用的就是這種算法差购。

標記-清除(Mark-and-Sweep)算法

這是一種更常見的垃圾回收算法四瘫。核心概念是對象是否可達,當一個對象不在任何地方被引用時欲逃,它就是不可達找蜜,相反就是可達。該算法會從根對象(全局對象稳析、函數(shù)執(zhí)行上下文)開始洗做,通過遍歷對象之間的引用關(guān)系,記錄所有可達和不可達的對象彰居。然后诚纸,回收器清除不可達的對象,釋放其內(nèi)存陈惰。

該算法解決了引用計數(shù)算法的循環(huán)引用問題畦徘,在剛剛那段代碼中,函數(shù) myFunction 執(zhí)行之后抬闯,對象 ab 從全局對象出發(fā)就無法獲取了井辆。因此,他們將會被回收画髓。

從 2012 年起掘剪,所有現(xiàn)代瀏覽器都使用了這種算法平委。

Chrome 和 nodejs 的垃圾回收算法

Chrome 和 nodejs 都采用了谷-歌開源的 V8 引擎奈虾。V8 引擎的垃圾回收機制采用了標記-清除算法,但在此基礎(chǔ)做了一些優(yōu)化廉赔。

V8 引擎將內(nèi)存分為新生代(Young Generation)和老生代(Old Generation)肉微。大多數(shù)對象在新生代中創(chuàng)建,經(jīng)過一定時間后蜡塌,如果它們?nèi)匀淮婊畹锬桑蜁粫x升到老生代。

Scavenger 垃圾回收(新生代)

新生代使用了 Scavenger 垃圾回收算法馏艾,它將內(nèi)存劃分為一個存活區(qū)域和一個空閑區(qū)域劳曹。對象首先被分配到存活區(qū)域奴愉,當存活區(qū)域滿時,會執(zhí)行垃圾回收操作铁孵,將存活的對象復制到空閑區(qū)域锭硼,并清空存活區(qū)域。

Mark-Sweep-Compact 垃圾回收(老生代)

老生代中使用了 Mark-Sweep-Compact(標記-清除-整理)垃圾回收算法蜕劝。它首先標記所有的存活對象檀头,然后清除掉未被標記的對象,最后進行內(nèi)存整理岖沛,使存活對象連續(xù)排列暑始,減少內(nèi)存碎片。

增量垃圾回收

V8 引擎還支持增量垃圾回收婴削。他會將垃圾回收操作分成多個小步驟執(zhí)行廊镜,每個步驟之間會插入一些 JavaScript 代碼的執(zhí)行,從而避免長時間的垃圾回收造成的界面卡頓馆蠕。

空閑時間垃圾回收

V8 引擎還在空閑時間執(zhí)行部分垃圾回收操作期升,以充分利用閑置的計算資源。這些時間段可能是在程序等待用戶輸入互躬、網(wǎng)絡(luò)請求返回播赁、或者其他暫時沒有任務(wù)需要處理的情況下出現(xiàn)的。

需要手動清除的內(nèi)存

垃圾回收機制會根據(jù)算法智能的回收大部分的內(nèi)存吼渡,但由于業(yè)務(wù)邏輯的關(guān)系容为,它無法明確知道在我們的寫的(垃圾)代碼中,哪些對象其實是不再使用的寺酪,所以我們在開發(fā)過程中需要及時的清除不需要的事件監(jiān)聽坎背、定時器、計時器寄雀,避免循環(huán)引用得滤,以及避免使用閉包。

清除事件監(jiān)聽

const myButton = document.getElementById("myButton")

function handleClick() {
  console.log("Button clicked!")
}

// 添加事件監(jiān)聽器
myButton.addEventListener("click", handleClick)

// 在頁面卸載或元素移除時解除事件監(jiān)聽器
window.addEventListener("beforeunload", () => {
  myButton.removeEventListener("click", handleClick)
})

執(zhí)清除定時器盒犹、計時器

const timer = setTimeout(() => {}, 500)

// 在頁面卸載或元素移除時解除事件監(jiān)聽器
window.addEventListener("beforeunload", () => {
  clearTimeout(timer)
})

手動調(diào)用垃圾回收

一般情況下我們無需手動調(diào)用垃圾回收懂更,但有些瀏覽器支持主動觸發(fā)垃圾回收。

IE 瀏覽器

if (typeof window.CollectGarbage === "function") {
  window.CollectGarbage()
}

Opera 瀏覽器

if (window.opera && typeof window.opera.collect === "function") {
  window.opera.collect()
}

總結(jié)

總而言之急膀,垃圾回收是一項必不可少的內(nèi)存管理機制沮协,希望大家都能理解其原理,并運用在實際開發(fā)中卓嫂,避免造成內(nèi)存泄漏的問題慷暂,提高應用程序的性能和用戶體驗。


參考文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晨雳,一起剝皮案震驚了整個濱河市行瑞,隨后出現(xiàn)的幾起案子奸腺,更是在濱河造成了極大的恐慌,老刑警劉巖血久,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洋机,死亡現(xiàn)場離奇詭異,居然都是意外死亡洋魂,警方通過查閱死者的電腦和手機绷旗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來副砍,“玉大人衔肢,你說我怎么就攤上這事』眙幔” “怎么了角骤?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長心剥。 經(jīng)常有香客問我邦尊,道長,這世上最難降的妖魔是什么优烧? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任蝉揍,我火速辦了婚禮,結(jié)果婚禮上畦娄,老公的妹妹穿的比我還像新娘又沾。我一直安慰自己,他們只是感情好熙卡,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布杖刷。 她就那樣靜靜地躺著,像睡著了一般驳癌。 火紅的嫁衣襯著肌膚如雪滑燃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天颓鲜,我揣著相機與錄音表窘,去河邊找鬼。 笑死灾杰,一個胖子當著我的面吹牛蚊丐,可吹牛的內(nèi)容都是我干的熙参。 我是一名探鬼主播艳吠,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孽椰!你這毒婦竟也來了昭娩?” 一聲冷哼從身側(cè)響起凛篙,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栏渺,沒想到半個月后呛梆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡磕诊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年填物,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霎终。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡滞磺,死狀恐怖肠缔,靈堂內(nèi)的尸體忽然破棺而出变隔,到底是詐尸還是另有隱情,我是刑警寧澤轿偎,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布广凸,位于F島的核電站阅茶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谅海。R本人自食惡果不足惜脸哀,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扭吁。 院中可真熱鬧企蹭,春花似錦、人聲如沸智末。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽系馆。三九已至送漠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間由蘑,已是汗流浹背闽寡。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尼酿,地道東北人爷狈。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像裳擎,于是被迫代替她去往敵國和親涎永。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容