深入淺析JS中的垃圾回收機制

2023-03-02 22:00:18

基本型別存放在棧中,參照型別存放在堆中。JavaScript 是在建立變數(物件,字串等)時自動進行了分配記憶體,並且在不使用它們時「自動」釋放。釋放的過程稱為垃圾回收。

垃圾回收策略

所有垃圾回收器都需要做的任務

  • 標記空間中活動(存活)物件和非活動(非存活)物件

  • 回收或者重用被非活動物件佔據的記憶體

  • 記憶體整理,防止記憶體碎片的出現

什麼是垃圾物件?

一般來說沒有被參照的物件就是垃圾,就是要被清除。從根開始遍歷物件。

例外
如果幾個物件參照形成一個環,互相參照,但根存取不到它們,這幾個物件也是垃圾,也要被清除。

什麼是根物件和存活物件

根物件
有一組基本的固有可達值,由於顯而易見的原因無法刪除

  • 全域性變數 window全域性物件、DOM檔案樹根物件等

存活物件
如果參照或參照鏈可以從根存取任何其他值,則認為該值是可存取的

V8引擎回收 分代回收法

將堆分為新生代和老生代。
新生代中存放的是生存時間短的物件,老生代中存放的生存時間久的物件。

新生代垃圾回收器 scavenge複製演演算法

堆記憶體分為兩部分,一個是使用區,處於使用狀態的空間;另一個是空閒區,處於空閒狀態的空間。

  • 新加入的物件會存放到使用區,當使用區快被寫滿時,就需要進行垃圾清理操作。

  • 新生代垃圾回收器會對使用區的活動物件物件做標記,標記完成之後將使用區的活動物件複製到空閒區。解決了記憶體散落分塊的問題。

  • 將使用區的非活動物件佔用的空間清理掉。最後進行角色互換,原來的使用區變成新的空閒區,原來的空閒區變成新的使用區。

移動到老生代的物件

  • 如果一個物件多次複製後仍然存活,將被認為時生命週期較長的物件,隨後被移動到老生代中。
  • 複製一個物件到空閒區時,佔用空閒區空間超過了25%,這個物件會被直接晉升到老生代空間中。原因是原來的空閒區會變成新的使用區,繼續進行物件記憶體的分配,若佔比過高,新物件的可用空間太少。

新生代優化 並行回收

全停頓問題
JavaScript是單執行緒語言,執行在主執行緒上,進行垃圾回收時會阻塞JavaScript指令碼的執行,需要等待垃圾回收完畢後再恢復指令碼執行。

如果一次GC的時間過長,可能造成頁面卡頓現象。

並行回收機制
垃圾回收器在主執行緒上執行的過程中,開啟多個輔助執行緒,同時執行同樣的回收工作。

在這裡插入圖片描述

老生代垃圾回收

使用scavenge方式存在的問題
1.存活物件較多,頻繁複制存活物件效率將降低
2.浪費一半空間

主要採用標記-清除法,在記憶體分配不足時,採用標記-整理

老年代垃圾回收期採用的演演算法
1. 首先使用標記-清除完成垃圾空間的回收;
2. 採用標記-整理進行空間優化;
3. 採用優化-增量標記與惰性清理進行效率優化;

標記-清除 與 標記-整理 演演算法

scavenge只複製活著的物件,而標記-清除只清除死了的物件。
活物件在新生代中只佔較少部分,死物件在老生代中只佔較少部分,這就是兩種回收方式都能高效處理的原因

缺點
記憶體碎片太多。如果出現需要分配一個大記憶體的情況,由於剩餘的碎片空間不足以完成此次分配,就會提前觸發垃圾回收,而這次回收是不必要的。

-> 標記-整理演演算法 標記完存活物件後,將存活物件向記憶體空間的一端移動,移動完成後,清除掉邊界外的所有記憶體

優化-增量標記與惰性清理

增量標記

如果有很多物件,並且我們試圖一次遍歷並標記整個物件集,那麼可能會花費一些時間,並在執行中會有一定的延遲。因此,引擎試圖將垃圾回收分解為多個部分。然後,各個部分分別執行。

V8對老生代垃圾回收器進行了優化,從全停頓標記切換到增量標記
將一次垃圾回收變成一小段一小段GC垃圾回收
在這裡插入圖片描述
如果採用非黑即白(存活和死亡)的標記策略,那在垃圾回收器執行了一段增量回收後,暫停後啟用主執行緒去執行了應用程式中的一段 JavaScript 程式碼,隨後當垃圾回收器再次被啟動,這時候記憶體中黑白色都有,我們無法得知下一步走到哪裡了

惰性清理

增量標記完成後,惰性清理就開始了。當增量標記完成後,假如當前的可用記憶體足以讓我們快速的執行程式碼,其實我們是沒必要立即清理記憶體的,可以將清理過程稍微延遲一下,讓 JavaScript 指令碼程式碼先執行,也無需一次性清理完所有非活動物件記憶體,可以按需逐一進行清理直到所有的非活動物件記憶體都清理完畢,後面再接著執行增量標記

三色標記法 暫停和恢復

三色標記法的 mark 操作可以漸進執行的而不需每次都掃描整個記憶體空間,可以很好的配合增量回收進行暫停恢復的一些操作,從而減少 全停頓 的時間

  • 白色:未被標記的物件
  • 灰色:自身被標記,該物件的參照物件未被標記
  • 黑色:自身和該物件的參照物件(箭頭指的物件)都被標記

在這裡插入圖片描述
從一組根物件開始,先將這組根物件標記為灰色並推入到標記工作表中,當回收器從標記工作表中彈出物件並存取它的參照物件時,將其自身由灰色轉變成黑色,並將自身的下一個參照物件轉為灰色

就這樣一直往下走,直到沒有可標記灰色的物件時,也就是無可達的物件了,那麼剩下的所有白色物件都是無法到達的,即等待回收。

當前記憶體中有沒有灰色節點來判斷整個標記是否完成,如沒有灰色節點,直接進入清理階段,如還有灰色標記,恢復時直接從灰色的節點開始繼續執行就可以

寫屏障

一次完整的GC標記分塊暫停後,執行任務程式,修改了物件的參照關係。
在這裡插入圖片描述
假設在第一次增量分段中全部將ABC標記為黑色,然後執行JavaScript指令碼,將B->D,開始執行第二次增量分段。

新物件D是初始的白色,但是此時沒有灰色物件了,也就是全部標記完成需要開始清理了,D將會在清理階段被回收。這是不對的。
V8引入寫屏障機制,一旦有黑色物件參照白色物件,該機制就將參照的白色物件變為灰色

並行回收
  • 並行回收會阻塞主執行緒
  • 增量標記增加了總暫停時間、降低應用程式吞吐量

主執行緒在執行 JavaScript 的過程中,輔助執行緒能夠在後臺完成執行垃圾回收的操作
在這裡插入圖片描述

【推薦學習:】

以上就是深入淺析JS中的垃圾回收機制的詳細內容,更多請關注TW511.COM其它相關文章!