從 4 個面試題了解「瀏覽器的垃圾回收」

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

咱們首先帶着這 4 個問題,來了解瀏覽器垃圾回收的過程,後面會逐一解答:web

  1. 瀏覽器怎麼進行垃圾回收?
  2. 瀏覽器中不一樣類型變量的內存都是什麼時候釋放?
  3. 哪些狀況會致使內存泄露?如何避免?
  4. weakMap  weakSet 和  Map  Set 有什麼區別?

ok, let's go!面試

什麼是垃圾數據?

生活中你買了一瓶可樂,喝完以後可樂瓶就變成了垃圾,應該被回收處理。算法

一樣地,咱們在寫 js 代碼的時候,會頻繁地操做數據。數組

在一些數據不被須要的時候,它就是垃圾數據,垃圾數據佔用的內存就應該被回收。瀏覽器

變量的生命週期

好比這麼一段代碼:微信

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

當 JavaScript 執行這段代碼的時候,數據結構

會先在全局做用域中添加一個dog 屬性,並在堆中建立了一個空對象,將該對象的地址指向了 dog閉包

隨後又建立一個大小爲 1 的數組,並將屬性地址指向了 dog.a。此時的內存佈局圖以下所示:app

若是此時,我將另一個對象賦給了 a 屬性,代碼以下所示:

dog.a = new Object()複製代碼

此時的內存佈局圖:

a 的指向改變了, 此時堆中的數組對象就成爲了避免被使用的數據,專業名詞叫「不可達」的數據。

這就是須要回收的垃圾數據。

垃圾回收算法

能夠將這個過程想象成從根溢出一個巨大的油漆桶,它從一個根節點出發將可到達的對象標記染色, 而後移除未標記的。

第一步:標記空間中「可達」值。

V8 採用的是可達性 (reachability) 算法來判斷堆中的對象應不該該被回收。

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

  • 從根節點(Root)出發,遍歷全部的對象。
  • 能夠遍歷到的對象,是可達的(reachable)。
  • 沒有被遍歷到的對象,不可達的(unreachable)。

在瀏覽器環境下,根節點有不少,主要包括這幾種:

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

這些根節點不是垃圾,不可能被回收。

第二步:回收「不可達」的值所佔據的內存。

在全部的標記完成以後,統一清理內存中全部不可達的對象。

第三步,作內存整理。

  • 在頻繁回收對象後,內存中就會存在大量不連續空間,專業名詞叫「內存碎片」。
  • 當內存中出現了大量的內存碎片,若是須要分配較大的連續內存時,就有可能出現內存不足的狀況。
  • 因此最後一步是整理內存碎片。(但這步實際上是可選的,由於有的垃圾回收器不會產生內存碎片,好比接下來咱們要介紹的副垃圾回收器。)

何時垃圾回收?

瀏覽器進行垃圾回收的時候,會暫停 JavaScript 腳本,等垃圾回收完畢再繼續執行。

對於普通應用這樣沒什麼問題,但對於 JS 遊戲、動畫對連貫性要求比較高的應用,若是暫停時間很長就會形成頁面卡頓。

這就是咱們接下來談的關於垃圾回收的問題:何時進行垃圾回收,能夠避免長時間暫停。

分代收集

瀏覽器將數據分爲兩種,一種是「臨時」對象,一種是「長久」對象。

  • 臨時對象:

    • 大部分對象在內存中存活的時間很短。
    • 好比函數內部聲明的變量,或者塊級做用域中的變量。當函數或者代碼塊執行結束時,做用域中定義的變量就會被銷燬。
    • 這類對象很快就變得不可訪問,應該快點回收。
  • 長久對象:

    • 生命週期很長的對象,好比全局的  window、DOM、Web API 等等。
    • 這類對象能夠慢點回收。

這兩種對象對應不一樣的回收策略,因此,V8 把堆分爲新生代和老生代兩個區域, 新生代中存放臨時對象,老生代中存放持久對象。

而且讓副垃圾回收器、主垃圾回收器,分別負責新生代、老生代的垃圾回收。

這樣就能夠實現高效的垃圾回收啦。

通常來講,面試回答到這就夠了。若是想和麪試官深刻交流,能夠繼續聊聊兩個垃圾回收器。

主垃圾回收器

負責老生代的垃圾回收,有兩個特色:

  1. 對象佔用空間大。
  2. 對象存活時間長。

它使用「標記-清除」的算法執行垃圾回收。

  1. 首先是標記。

    • 從一組根元素開始,遞歸遍歷這組根元素。
    • 在這個遍歷過程當中,能到達的元素稱爲活動對象,沒有到達的元素就能夠判斷爲垃圾數據。
  2. 而後是垃圾清除。直接將標記爲垃圾的數據清理掉。

  3. 屢次標記-清除後,會產生大量不連續的內存碎片,須要進行內存整理。

副垃圾回收器

負責新生代的垃圾回收,一般只支持 1~8 M 的容量。

新生代被分爲兩個區域:通常是對象區域,一半是空閒區域。新加入的對象都被放入對象區域,等對象區域快滿的時候,會執行一次垃圾清理。

  1. 先給對象區域全部垃圾作標記。
  2. 標記完成後,存活的對象被複制到空閒區域,而且將他們有序的排列一遍。 這就回到咱們前面留下的問題 -- 副垃圾回收器沒有碎片整理。由於空閒區域裏此時是有序的,沒有碎片,也就不須要整理了。
  3. 複製完成後,對象區域會和空閒區域進行對調。將空閒區域中存活的對象放入對象區域裏。 這樣,就完成了垃圾回收。

由於副垃圾回收器操做比較頻繁,因此爲了執行效率,通常新生區的空間會被設置得比較小。

一旦檢測到空間裝滿了,就執行垃圾回收。

分代收集

一句話總結分代回收就是:將堆分爲新生代與老生代,多回收新生代,少回收老生代。

這樣就減小了每次需遍歷的對象,從而減小每次垃圾回收的耗時。

增量收集

若是腳本中有許多對象,引擎一次性遍歷整個對象,會形成一個長時間暫停。

因此引擎將垃圾收集工做分紅更小的塊,每次處理一部分,屢次處理。

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

閒時收集

垃圾收集器只會在 CPU 空閒時嘗試運行,以減小可能對代碼執行的影響。

面試題1:瀏覽器怎麼進行垃圾回收?

從三個點來回答什麼是垃圾、如何撿垃圾、何時撿垃圾。

  1. 什麼是垃圾

    • 再也不須要,即爲垃圾
    • 全局變量隨時可能用到,因此必定不是垃圾
  2. 如何撿垃圾(遍歷算法)

    • 標記空間中「可達」值。

      - 從根節點(Root)出發,遍歷全部的對象。
      - 能夠遍歷到的對象,是可達的(reachable)。
      - 沒有被遍歷到的對象,不可達的(unreachable)
    • 回收「不可達」的值所佔據的內存。

    • 作內存整理。

  3. 何時撿垃圾

    • 前端有其特殊性,垃圾回收的時候會形成頁面卡頓。
    • 分代收集、增量收集、閒時收集。

面試題2:瀏覽器中不一樣類型變量的內存都是什麼時候釋放?

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

  • 引用類型

    • 在沒有引用以後,經過 V8 自動回收。
  • 值類型

    • 若是處於閉包的狀況下,要等閉包沒有引用纔會被 V8 回收。
    • 非閉包的狀況下,等待 V8 的新生代切換的時候回收。

面試題3:哪些狀況會致使內存泄露?如何避免?

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

以 Vue 爲例,一般有這些狀況:

  • 監聽在  window/body 等事件沒有解綁
  • 綁在  EventBus 的事件沒有解綁
  • Vuex 的  $storewatch 了以後沒有  unwatch
  • 使用第三方庫建立,沒有調用正確的銷燬函數

解決辦法:beforeDestroy 中及時銷燬

  • 綁定了  DOM/BOM 對象中的事件  addEventListener , removeEventListener
  • 觀察者模式  $on$off處理。
  • 若是組件中使用了定時器,應銷燬處理。
  • 若是在  mounted/created 鉤子中使用了第三方庫初始化,對應的銷燬。
  • 使用弱引用  weakMapweakSet

閉包會致使內存泄露嗎?

順便說一個我在瞭解垃圾回收以前對閉包的誤解。

閉包會致使內存泄露嗎?正確的答案是不會。

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

閉包裏面的變量就是咱們須要的變量,不能說是內存泄露。

這個誤解是如何來的?由於 IE。IE 有 bug,IE 在咱們使用完閉包以後,依然回收不了閉包裏面引用的變量。這是 IE 的問題,不是閉包的問題。參考這篇文章

面試題4:weakMap weakSet 和 Map Set 有什麼區別?

在 ES6 中爲咱們新增了兩個數據結構 WeakMap、WeakSet,就是爲了解決內存泄漏的問題。

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

只要所引用的對象的其餘引用都被清除,垃圾回收機制就會釋放該對象所佔用的內存。

也就是說,一旦再也不須要,WeakMap 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。

更全面的介紹能夠看這裏:第 4 題:介紹下 Set、Map、WeakSet 和 WeakMap 的區別

總結

如今咱們簡單瞭解了瀏覽器的垃圾回收機制,還記得最初的 4 個問題嗎?

  1. 瀏覽器怎麼進行垃圾回收?

答題思路:什麼是垃圾、怎麼收垃圾、何時收垃圾。

  1. 瀏覽器中不一樣類型變量的內存都是什麼時候釋放?

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

  1. 哪些狀況會致使內存泄露?如何避免?

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

  1. weakMap  weakSet 和  Map  Set 有什麼區別?

答題思路:WeakMapWeakSet 弱引用,解決了內存泄露問題。

源自:https://juejin.im/post/6861967094318284814

聲明:文章著做權歸做者全部,若有侵權,請聯繫小編刪除。

感謝 · 轉發歡迎你們留言

本文分享自微信公衆號 - web前端學習圈(web-xxq)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索