JavaScript引擎的內存空間主要分爲棧和堆。html
棧
棧是臨時存儲空間,主要存儲局部變量和函數調用。web
基本類型數據(Number, Boolean, String, Null, Undefined, Symbol, BigInt)保存在在棧內存中。引用類型數據保存在堆內存中,引用數據類型的變量是一個指向堆內存中實際對象的引用,存在棧中。算法
基本類型賦值,系統會爲新的變量在棧內存中分配一個新值,這個很好理解。引用類型賦值,系統會爲新的變量在棧內存中分配一個值,這個值僅僅是指向同一個對象的引用,和原對象指向的都是堆內存中的同一個對象。segmentfault
對於函數,解釋器建立了」調用棧「來記錄函數的調用過程。每調用一個函數,解釋器就能夠把該函數添加進調用棧,解釋器會爲被添加進來的函數建立一個棧幀(用來保存函數的局部變量以及執行語句)並當即執行。若是正在執行的函數還調用了其餘函數,新函數會繼續被添加進入調用棧。函數執行完成,對應的棧幀當即被銷燬。瀏覽器
兩種查看調用棧的方法微信
-
使用 console.trace() [1] 向Web控制檯輸出一個堆棧跟蹤. -
瀏覽器開發者工具進行斷點調試
棧雖然很輕量,在使用時建立,使用結束後銷燬,可是不是能夠無限增加的,被分配的調用棧空間被佔滿時,就會引發」棧溢出「的錯誤。編輯器
(function foo() { foo()})()
![](http://static.javashuo.com/static/loading.gif)
爲何基本數據類型存儲在棧中,引用數據類型存儲在堆中?函數
JavaScript引擎須要用棧來維護程序執行期間的上下文的狀態,若是棧空間大了的話,全部數據都存放在棧空間裏面,會影響到上下文切換的效率,進而影響整個程序的執行效率。工具
堆
堆空間存儲的數據比較複雜,大體能夠劃分爲下面 5 個區域:代碼區(Code Space)、Map 區(Map Space)、大對象區(Large Object Space)、新生代(New Space)、老生代(Old Space)。本篇文章主要討論新生代和老生代的內存回收算法。post
新生代內存是臨時分配的內存,存活時間段,老生代內存是常駐內存,存活時間長。
![](http://static.javashuo.com/static/loading.gif)
新生代內存回收
新生代中用 Scavenge 算法來處理。所謂 Scavenge 算法,是把新生代空間對半劃分爲兩個區域,一半是對象區域(from),一半是空閒區域 (to)。
新的對象會首先被分配到 from 空間,當進行垃圾回收的時候,會先將 from 空間中的 存活的對象複製到 to 空間進行保存,對未存活的對象的空間進行回收。複製完成後, from 空間和 to 空間進行調換,to 空間會變成新的 from 空間,原來的 from 空間則變成 to 空間。這種算法稱之爲 」Scavenge「。
![](http://static.javashuo.com/static/loading.gif)
新生代內存回收頻率很高,速度也很快,可是空間利用率很低,由於有一半的內存空間處於"閒置"狀態。
老生代內存回收
新生代中屢次進行回收仍然存活的對象會被轉移到空間較大的老生代內存中,這種現象稱爲晉升。如下兩種狀況
-
在垃圾回收過程當中,發現某個對象以前被清理過,那麼將會晉升到老生代的內存空間中 -
在 from 空間和 to 空間進行反轉的過程當中,若是 to 空間中的使用量已經超過了 25% ,那麼就講 from 中的對象直接晉升到老生代內存空間中。
由於老生代空間較大,若是仍然用 Scavenge 算法來頻繁複制對象,那麼性能開銷就太大了。
標記-清除(Mark-Sweep)
老生代採用的是」標記清除「來回收未存活的對象。
分爲標記和清除兩個階段。標記階段會遍歷堆中全部的對象,並對存活的對象進行標記,清除階段則是對未標記的對象進行清除。
標記-整理(Mark-Compact)
標記清除不會對內存一分爲二,因此不會浪費空間。可是通過標記清除以後的內存空間會生產不少不連續的碎片空間,這種不連續的碎片空間中,在遇到較大的對象時可能會因爲空間不足而致使沒法存儲。爲了解決內存碎片的問題,須要使用另一種算法 - 標記-整理(Mark-Compact)。標記整理對待未存活對象不是當即回收,而是將存活對象移動到一邊,而後直接清掉端邊界之外的內存。
![](http://static.javashuo.com/static/loading.gif)
增量標記
爲了不出現JavaScript應用程序與垃圾回收器看到的不一致的狀況,進行垃圾回收的時候,都須要將正在運行的程序停下來,等待垃圾回收執行完成以後再回復程序的執行,這種現象稱爲「全停頓」。若是須要回收的數據過多,那麼全停頓的時候就會比較長,會影響其餘程序的正常執行。
爲了不垃圾回收時間過長影響其餘程序的執行,V8將標記過程分紅一個個小的子標記過程,同時讓垃圾回收和JavaScript應用邏輯代碼交替執行,直到標記階段完成。咱們稱這個過程爲增量標記算法。通俗理解,就是把垃圾回收這個大的任務分紅一個個小任務,穿插在 JavaScript任務中間執行,這個過程其實跟 React Fiber 的設計思路相似。
參考
-
V8 引擎垃圾內存回收原理解析 [2] -
JavaScript中V8引擎內存問題 [3] -
淺談V8引擎中的垃圾回收機制 [4] -
《深刻淺出Node.js》
參考資料
console.trace(): https://developer.mozilla.org/zh-CN/docs/Web/API/Console/trace
[2]V8 引擎垃圾內存回收原理解析: https://juejin.im/post/5dcb7f706fb9a04aad01615a
[3]JavaScript中V8引擎內存問題: https://www.cnblogs.com/cangqinglang/p/12668374.html
[4]淺談V8引擎中的垃圾回收機制: https://segmentfault.com/a/1190000000440270
本文分享自微信公衆號 - 牧碼的星星(gh_0d71d9e8b1c3)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。