<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【面試題】727- 從 4 個(gè)面試題了解「瀏覽器的垃圾回收」

          共 4067字,需瀏覽 9分鐘

           ·

          2020-09-25 13:11



          來源:Marica

          https://juejin.im/post/6861967094318284814

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

          我們首先帶著這 4 個(gè)問題,來了解瀏覽器垃圾回收的過程,后面會(huì)逐一解答:

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

          ok, let's go!

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

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

          同樣地,我們?cè)趯?js 代碼的時(shí)候,會(huì)頻繁地操作數(shù)據(jù)。

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

          變量的生命周期

          比如這么一段代碼:

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

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

          會(huì)先在全局作用域中添加一個(gè)dog 屬性,并在堆中創(chuàng)建了一個(gè)空對(duì)象,將該對(duì)象的地址指向了 dog。

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

          如果此時(shí),我將另外一個(gè)對(duì)象賦給了 a 屬性,代碼如下所示:

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

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

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

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

          垃圾回收算法

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

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

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

          這個(gè)算法的思路是這樣的:

          • 從根節(jié)點(diǎn)(Root)出發(fā),遍歷所有的對(duì)象。
          • 可以遍歷到的對(duì)象,是可達(dá)的(reachable)。
          • 沒有被遍歷到的對(duì)象,不可達(dá)的(unreachable)。

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

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

          這些根節(jié)點(diǎn)不是垃圾,不可能被回收。

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

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

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

          • 在頻繁回收對(duì)象后,內(nèi)存中就會(huì)存在大量不連續(xù)空間,專業(yè)名詞叫「內(nèi)存碎片」。
          • 當(dāng)內(nèi)存中出現(xiàn)了大量的內(nèi)存碎片,如果需要分配較大的連續(xù)內(nèi)存時(shí),就有可能出現(xiàn)內(nèi)存不足的情況。
          • 所以最后一步是整理內(nèi)存碎片。(但這步其實(shí)是可選的,因?yàn)橛械睦厥掌鞑粫?huì)產(chǎn)生內(nèi)存碎片,比如接下來我們要介紹的副垃圾回收器。)

          什么時(shí)候垃圾回收?

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

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

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

          分代收集

          瀏覽器將數(shù)據(jù)分為兩種,一種是「臨時(shí)」對(duì)象,一種是「長久」對(duì)象。

          • 臨時(shí)對(duì)象:

            • 大部分對(duì)象在內(nèi)存中存活的時(shí)間很短。
            • 比如函數(shù)內(nèi)部聲明的變量,或者塊級(jí)作用域中的變量。當(dāng)函數(shù)或者代碼塊執(zhí)行結(jié)束時(shí),作用域中定義的變量就會(huì)被銷毀。
            • 這類對(duì)象很快就變得不可訪問,應(yīng)該快點(diǎn)回收。
          • 長久對(duì)象:

            • 生命周期很長的對(duì)象,比如全局的 window、DOM、Web API 等等。
            • 這類對(duì)象可以慢點(diǎn)回收。

          這兩種對(duì)象對(duì)應(yīng)不同的回收策略,所以,V8 把堆分為新生代和老生代兩個(gè)區(qū)域, 新生代中存放臨時(shí)對(duì)象,老生代中存放持久對(duì)象。

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

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

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

          主垃圾回收器

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

          1. 對(duì)象占用空間大。
          2. 對(duì)象存活時(shí)間長。

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

          1. 首先是標(biāo)記。

            • 從一組根元素開始,遞歸遍歷這組根元素。
            • 在這個(gè)遍歷過程中,能到達(dá)的元素稱為活動(dòng)對(duì)象,沒有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)。
          2. 然后是垃圾清除。 直接將標(biāo)記為垃圾的數(shù)據(jù)清理掉。

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

          副垃圾回收器

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

          新生代被分為兩個(gè)區(qū)域:一般是對(duì)象區(qū)域,一半是空閑區(qū)域。 新加入的對(duì)象都被放入對(duì)象區(qū)域,等對(duì)象區(qū)域快滿的時(shí)候,會(huì)執(zhí)行一次垃圾清理。

          1. 先給對(duì)象區(qū)域所有垃圾做標(biāo)記。
          2. 標(biāo)記完成后,存活的對(duì)象被復(fù)制到空閑區(qū)域,并且將他們有序的排列一遍。 這就回到我們前面留下的問題 -- 副垃圾回收器沒有碎片整理。因?yàn)榭臻e區(qū)域里此時(shí)是有序的,沒有碎片,也就不需要整理了。
          3. 復(fù)制完成后,對(duì)象區(qū)域會(huì)和空閑區(qū)域進(jìn)行對(duì)調(diào)。將空閑區(qū)域中存活的對(duì)象放入對(duì)象區(qū)域里。 這樣,就完成了垃圾回收。

          因?yàn)楦崩厥掌鞑僮鞅容^頻繁,所以為了執(zhí)行效率,一般新生區(qū)的空間會(huì)被設(shè)置得比較小。

          一旦檢測(cè)到空間裝滿了,就執(zhí)行垃圾回收。

          分代收集

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

          這樣就減少了每次需遍歷的對(duì)象,從而減少每次垃圾回收的耗時(shí)。

          增量收集

          如果腳本中有許多對(duì)象,引擎一次性遍歷整個(gè)對(duì)象,會(huì)造成一個(gè)長時(shí)間暫停。

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

          這樣就解決了長時(shí)間停頓的問題。

          閑時(shí)收集

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

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

          從三個(gè)點(diǎn)來回答什么是垃圾、如何撿垃圾、什么時(shí)候撿垃圾。

          1. 什么是垃圾

            • 不再需要,即為垃圾
            • 全局變量隨時(shí)可能用到,所以一定不是垃圾
          2. 如何撿垃圾(遍歷算法)

            • 標(biāo)記空間中「可達(dá)」值。

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

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

          3. 什么時(shí)候撿垃圾

            • 前端有其特殊性,垃圾回收的時(shí)候會(huì)造成頁面卡頓。
            • 分代收集、增量收集、閑時(shí)收集。

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

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

          • 引用類型

            • 在沒有引用之后,通過 V8 自動(dòng)回收。
          • 值類型

            • 如果處于閉包的情況下,要等閉包沒有引用才會(huì)被 V8 回收。
            • 非閉包的情況下,等待 V8 的新生代切換的時(shí)候回收。

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

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

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

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

          解決辦法:beforeDestroy 中及時(shí)銷毀

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

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

          順便說一個(gè)我在了解垃圾回收之前對(duì)閉包的誤解。

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

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

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

          這個(gè)誤解是如何來的?因?yàn)?IE。IE 有 bug,IE 在我們使用完閉包之后,依然回收不了閉包里面引用的變量。這是 IE 的問題,不是閉包的問題。參考這篇文章

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

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

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

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

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

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

          總結(jié)

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

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

          答題思路:什么是垃圾、怎么收垃圾、什么時(shí)候收垃圾。

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

          答題思路:分為值類型、引用類型。

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

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

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

          答題思路:WeakMap、WeakSet 弱引用,解決了內(nèi)存泄露問題。


          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 80+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 80+ 篇原創(chuàng)文章

          瀏覽 17
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  处一女一级a一片视频 | 欧美人与性口牲恔配上海 | 无一区二区三区 | 国产天天操女人 | 三级片在线观看视频网址 |