4 個問題圖解瀏覽器垃圾回收的過程

瀏覽器垃圾回收一直是前端面試常考的部分征绎,我一直不太理解蹲姐。最近深入學(xué)習(xí)了一下,爭取一篇文章說清楚。

我們首先帶著這 4 個問題柴墩,來了解瀏覽器垃圾回收的過程忙厌,后面會逐一解答:

  1. 瀏覽器怎么進(jìn)行垃圾回收?
  2. 瀏覽器中不同類型變量的內(nèi)存都是何時釋放江咳?
  3. 哪些情況會導(dǎo)致內(nèi)存泄露逢净?如何避免?
  4. weakMap weakSetMap Set 有什么區(qū)別歼指?

ok, let's go爹土!

什么是垃圾數(shù)據(jù)?

生活中你買了一瓶可樂,喝完之后可樂瓶就變成了垃圾踩身,應(yīng)該被回收處理着饥。

同樣地,我們在寫 js 代碼的時候惰赋,會頻繁地操作數(shù)據(jù)宰掉。

在一些數(shù)據(jù)不被需要的時候,它就是垃圾數(shù)據(jù)赁濒,垃圾數(shù)據(jù)占用的內(nèi)存就應(yīng)該被回收轨奄。

變量的生命周期

比如這么一段代碼:

let dog = new Object()let dog.a = new Array(1)

當(dāng) JavaScript 執(zhí)行這段代碼的時候,

會先在全局作用域中添加一個dog 屬性拒炎,并在堆中創(chuàng)建了一個空對象挪拟,將該對象的地址指向了 dog

隨后又創(chuàng)建一個大小為 1 的數(shù)組击你,并將屬性地址指向了 dog.a玉组。此時的內(nèi)存布局圖如下所示:

image

如果此時,我將另外一個對象賦給了 a 屬性丁侄,代碼如下所示:

dog.a = new Object()復(fù)制代碼

此時的內(nèi)存布局圖:
image

a 的指向改變了惯雳, 此時堆中的數(shù)組對象就成為了不被使用的數(shù)據(jù),專業(yè)名詞叫「不可達(dá)」的數(shù)據(jù)鸿摇。

這就是需要回收的垃圾數(shù)據(jù)石景。

垃圾回收算法

可以將這個過程想象成從根溢出一個巨大的油漆桶,它從一個根節(jié)點出發(fā)將可到達(dá)的對象標(biāo)記染色拙吉, 然后移除未標(biāo)記的潮孽。

第一步:標(biāo)記空間中「可達(dá)」值。

V8 采用的是可達(dá)性 (reachability) 算法來判斷堆中的對象應(yīng)不應(yīng)該被回收筷黔。

這個算法的思路是這樣的:

  • 從根節(jié)點(Root)出發(fā)往史,遍歷所有的對象。
  • 可以遍歷到的對象佛舱,是可達(dá)的(reachable)椎例。
  • 沒有被遍歷到的對象挨决,不可達(dá)的(unreachable)。

在瀏覽器環(huán)境下粟矿,根節(jié)點有很多凰棉,主要包括這幾種:

  • 全局變量 window,位于每個 iframe
  • 文檔 DOM
  • 存放在棧上的變量
  • ...

這些根節(jié)點不是垃圾陌粹,不可能被回收撒犀。

第二步:回收「不可達(dá)」的值所占據(jù)的內(nèi)存。

在所有的標(biāo)記完成之后掏秩,統(tǒng)一清理內(nèi)存中所有不可達(dá)的對象或舞。

第三步,做內(nèi)存整理蒙幻。

  • 在頻繁回收對象后映凳,內(nèi)存中就會存在大量不連續(xù)空間,專業(yè)名詞叫「內(nèi)存碎片」邮破。
  • 當(dāng)內(nèi)存中出現(xiàn)了大量的內(nèi)存碎片诈豌,如果需要分配較大的連續(xù)內(nèi)存時,就有可能出現(xiàn)內(nèi)存不足的情況抒和。
  • 所以最后一步是整理內(nèi)存碎片矫渔。(但這步其實是可選的,因為有的垃圾回收器不會產(chǎn)生內(nèi)存碎片摧莽,比如接下來我們要介紹的副垃圾回收器庙洼。)

什么時候垃圾回收?

瀏覽器進(jìn)行垃圾回收的時候镊辕,會暫停 JavaScript 腳本油够,等垃圾回收完畢再繼續(xù)執(zhí)行。

對于普通應(yīng)用這樣沒什么問題征懈,但對于 JS 游戲石咬、動畫對連貫性要求比較高的應(yīng)用,如果暫停時間很長就會造成頁面卡頓受裹。

這就是我們接下來談的關(guān)于垃圾回收的問題:什么時候進(jìn)行垃圾回收碌补,可以避免長時間暫停。

分代收集

瀏覽器將數(shù)據(jù)分為兩種棉饶,一種是「臨時」對象,一種是「長久」對象镇匀。

  • 臨時對象:

  • 大部分對象在內(nèi)存中存活的時間很短照藻。

  • 比如函數(shù)內(nèi)部聲明的變量,或者塊級作用域中的變量汗侵。當(dāng)函數(shù)或者代碼塊執(zhí)行結(jié)束時幸缕,作用域中定義的變量就會被銷毀群发。

  • 這類對象很快就變得不可訪問,應(yīng)該快點回收发乔。

  • 長久對象:

  • 生命周期很長的對象熟妓,比如全局的 window、DOM栏尚、Web API 等等起愈。

  • 這類對象可以慢點回收。

這兩種對象對應(yīng)不同的回收策略译仗,所以抬虽,V8 把堆分為新生代和老生代兩個區(qū)域, 新生代中存放臨時對象纵菌,老生代中存放持久對象阐污。

并且讓副垃圾回收器、主垃圾回收器咱圆,分別負(fù)責(zé)新生代笛辟、老生代的垃圾回收。

這樣就可以實現(xiàn)高效的垃圾回收啦序苏。

一般來說手幢,面試回答到這就夠了。如果想和面試官深入交流杠览,可以繼續(xù)聊聊兩個垃圾回收器弯菊。

主垃圾回收器

負(fù)責(zé)老生代的垃圾回收,有兩個特點:

  1. 對象占用空間大踱阿。
  2. 對象存活時間長管钳。

它使用「標(biāo)記-清除」的算法執(zhí)行垃圾回收。

  1. 首先是標(biāo)記软舌。
  • 從一組根元素開始才漆,遞歸遍歷這組根元素。
  • 在這個遍歷過程中佛点,能到達(dá)的元素稱為活動對象醇滥,沒有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)。
  1. 然后是垃圾清除超营。
    image

    直接將標(biāo)記為垃圾的數(shù)據(jù)清理掉鸳玩。

  2. 多次標(biāo)記-清除后,會產(chǎn)生大量不連續(xù)的內(nèi)存碎片演闭,需要進(jìn)行內(nèi)存整理不跟。
    image

副垃圾回收器

負(fù)責(zé)新生代的垃圾回收,通常只支持 1~8 M 的容量米碰。

新生代被分為兩個區(qū)域:一般是對象區(qū)域窝革,一半是空閑區(qū)域购城。
image

新加入的對象都被放入對象區(qū)域,等對象區(qū)域快滿的時候虐译,會執(zhí)行一次垃圾清理瘪板。

  1. 先給對象區(qū)域所有垃圾做標(biāo)記。

  2. 標(biāo)記完成后漆诽,存活的對象被復(fù)制到空閑區(qū)域侮攀,并且將他們有序的排列一遍。
    image

    這就回到我們前面留下的問題 -- 副垃圾回收器沒有碎片整理拴泌。因為空閑區(qū)域里此時是有序的魏身,沒有碎片,也就不需要整理了蚪腐。

  3. 復(fù)制完成后箭昵,對象區(qū)域會和空閑區(qū)域進(jìn)行對調(diào)。將空閑區(qū)域中存活的對象放入對象區(qū)域里回季。
    image

    這樣家制,就完成了垃圾回收。

因為副垃圾回收器操作比較頻繁泡一,所以為了執(zhí)行效率颤殴,一般新生區(qū)的空間會被設(shè)置得比較小。

一旦檢測到空間裝滿了鼻忠,就執(zhí)行垃圾回收涵但。

分代收集

一句話總結(jié)分代回收就是:將堆分為新生代與老生代,多回收新生代帖蔓,少回收老生代矮瘟。

這樣就減少了每次需遍歷的對象塑娇,從而減少每次垃圾回收的耗時哨啃。
image

增量收集

如果腳本中有許多對象珍特,引擎一次性遍歷整個對象,會造成一個長時間暫停宋距。

所以引擎將垃圾收集工作分成更小的塊,每次處理一部分,多次處理。

這樣就解決了長時間停頓的問題。
image

閑時收集

垃圾收集器只會在 CPU 空閑時嘗試運(yùn)行桦他,以減少可能對代碼執(zhí)行的影響圆仔。

面試題1:瀏覽器怎么進(jìn)行垃圾回收拦宣?

從三個點來回答什么是垃圾绸罗、如何撿垃圾、什么時候撿垃圾。

  1. 什么是垃圾
  • 不再需要,即為垃圾
  • 全局變量隨時可能用到,所以一定不是垃圾
  1. 如何撿垃圾(遍歷算法)
  • 標(biāo)記空間中「可達(dá)」值癣漆。

    - 從根節(jié)點(Root)出發(fā),遍歷所有的對象。- 可以遍歷到的對象旬痹,是可達(dá)的(reachable)附井。- 沒有被遍歷到的對象,不可達(dá)的(unreachable)
    
  • 回收「不可達(dá)」的值所占據(jù)的內(nèi)存两残。

  • 做內(nèi)存整理永毅。

  1. 什么時候撿垃圾
  • 前端有其特殊性,垃圾回收的時候會造成頁面卡頓人弓。
  • 分代收集沼死、增量收集、閑時收集崔赌。

面試題2:瀏覽器中不同類型變量的內(nèi)存都是何時釋放意蛀?

Javascritp 中類型:值類型,引用類型健芭。

  • 引用類型

  • 在沒有引用之后县钥,通過 V8 自動回收。

  • 值類型

  • 如果處于閉包的情況下慈迈,要等閉包沒有引用才會被 V8 回收若贮。

  • 非閉包的情況下,等待 V8 的新生代切換的時候回收。

面試題3:哪些情況會導(dǎo)致內(nèi)存泄露谴麦?如何避免蠢沿?

內(nèi)存泄露是指你「用不到」(訪問不到)的變量,依然占居著內(nèi)存空間细移,不能被再次利用起來搏予。

以 Vue 為例,通常有這些情況:

  • 監(jiān)聽在 window/body 等事件沒有解綁
  • 綁在 EventBus 的事件沒有解綁
  • Vuex$storewatch 了之后沒有 unwatch
  • 使用第三方庫創(chuàng)建腊徙,沒有調(diào)用正確的銷毀函數(shù)

解決辦法:beforeDestroy 中及時銷毀

  • 綁定了 DOM/BOM 對象中的事件 addEventListener 赚窃,removeEventListener
  • 觀察者模式 $on衰齐,$off處理。
  • 如果組件中使用了定時器,應(yīng)銷毀處理代乃。
  • 如果在 mounted/created 鉤子中使用了第三方庫初始化,對應(yīng)的銷毀仿粹。
  • 使用弱引用 weakMap搁吓、weakSet

閉包會導(dǎo)致內(nèi)存泄露嗎吭历?

順便說一個我在了解垃圾回收之前對閉包的誤解堕仔。

閉包會導(dǎo)致內(nèi)存泄露嗎?正確的答案是不會晌区。

內(nèi)存泄露是指你「用不到」(訪問不到)的變量摩骨,依然占居著內(nèi)存空間,不能被再次利用起來朗若。

閉包里面的變量就是我們需要的變量恼五,不能說是內(nèi)存泄露。

這個誤解是如何來的哭懈?因為 IE灾馒。IE 有 bug,IE 在我們使用完閉包之后遣总,依然回收不了閉包里面引用的變量睬罗。這是 IE 的問題,不是閉包的問題彤避。參考這篇文章

面試題4:weakMap weakSetMap Set 有什么區(qū)別傅物?

在 ES6 中為我們新增了兩個數(shù)據(jù)結(jié)構(gòu) WeakMap、WeakSet琉预,就是為了解決內(nèi)存泄漏的問題董饰。

它的鍵名所引用的對象都是弱引用,就是垃圾回收機(jī)制遍歷的時候不考慮該引用。

只要所引用的對象的其他引用都被清除卒暂,垃圾回收機(jī)制就會釋放該對象所占用的內(nèi)存啄栓。

也就是說,一旦不再需要也祠,WeakMap 里面的鍵名對象和所對應(yīng)的鍵值對會自動消失昙楚,不用手動刪除引用。

更全面的介紹可以看這里:第 4 題:介紹下 Set诈嘿、Map堪旧、WeakSet 和 WeakMap 的區(qū)別

總結(jié)

現(xiàn)在我們簡單了解了瀏覽器的垃圾回收機(jī)制,還記得最初的 4 個問題嗎奖亚?

  1. 瀏覽器怎么進(jìn)行垃圾回收淳梦?

答題思路:什么是垃圾、怎么收垃圾昔字、什么時候收垃圾爆袍。

  1. 瀏覽器中不同類型變量的內(nèi)存都是何時釋放?

答題思路:分為值類型作郭、引用類型陨囊。

  1. 哪些情況會導(dǎo)致內(nèi)存泄露?如何避免夹攒?

答題思路:內(nèi)存泄露是指你「用不到」(訪問不到)的變量蜘醋,依然占居著內(nèi)存空間,不能被再次利用起來芹助。

  1. weakMap weakSetMap Set 有什么區(qū)別堂湖?

答題思路:WeakMapWeakSet 弱引用状土,解決了內(nèi)存泄露問題无蜂。

看完三件事??

1、如果你覺得這篇內(nèi)容對你還蠻有幫助蒙谓,我想邀請你幫我三個小忙:

2斥季、點贊,轉(zhuǎn)發(fā)累驮,有你們的 『點贊和評論』酣倾,才是我創(chuàng)造的動力。

3谤专、關(guān)注公眾號 『 Java斗帝 』躁锡,不定期分享原創(chuàng)知識。

4置侍、同時可以期待后續(xù)文章ing??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末映之,一起剝皮案震驚了整個濱河市拦焚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杠输,老刑警劉巖赎败,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蠢甲,居然都是意外死亡僵刮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門鹦牛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搞糕,“玉大人,你說我怎么就攤上這事能岩∧” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵拉鹃,是天一觀的道長。 經(jīng)常有香客問我鲫忍,道長膏燕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任悟民,我火速辦了婚禮坝辫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘射亏。我一直安慰自己近忙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布智润。 她就那樣靜靜地躺著及舍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窟绷。 梳的紋絲不亂的頭發(fā)上锯玛,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音兼蜈,去河邊找鬼攘残。 笑死,一個胖子當(dāng)著我的面吹牛为狸,可吹牛的內(nèi)容都是我干的歼郭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼辐棒,長吁一口氣:“原來是場噩夢啊……” “哼病曾!你這毒婦竟也來了牍蜂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤知态,失蹤者是張志新(化名)和其女友劉穎捷兰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體负敏,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡贡茅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了其做。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顶考。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妖泄,靈堂內(nèi)的尸體忽然破棺而出驹沿,到底是詐尸還是另有隱情,我是刑警寧澤蹈胡,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布渊季,位于F島的核電站,受9級特大地震影響罚渐,放射性物質(zhì)發(fā)生泄漏却汉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一荷并、第九天 我趴在偏房一處隱蔽的房頂上張望合砂。 院中可真熱鬧,春花似錦源织、人聲如沸翩伪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘屹。三九已至,卻和暖如春黎茎,著一層夾襖步出監(jiān)牢的瞬間囊颅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工傅瞻, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留踢代,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓嗅骄,卻偏偏與公主長得像胳挎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子溺森,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348