微信公衆號:[前端一鍋煮]
一點技術、一點思考。
JavaScript 引擎的內存空間主要分爲棧和堆。前端
V8 的垃圾回收策略主要基於分代式垃圾回收機制。按照對象的存活時間將內存的垃圾回收進行不一樣分代,而後分別對不一樣分代的內存使用最適合的算法。主要分爲新生代和老生代,有標記清除、標記整理、增量標記等方法。vue
棧是臨時存儲空間,主要存儲局部變量和函數調用。node
基本類型賦值(Number, Boolean, String, Null, Undefined, Symbol, BigInt),系統會爲新的變量在棧內存中分配一個新值。web
引用類型賦值,系統會爲新的變量在棧內存中分配一個值,這個值僅僅是指向同一個對象的引用,和原對象指向的都是堆內存中的同一個對象。算法
對於函數,解釋器建立了」調用棧「來記錄函數的調用過程。每調用一個函數,解釋器就把該函數添加進調用棧,解釋器會爲被添加進來的函數建立一個棧幀(用來保存函數的局部變量以及執行語句)並當即執行。若是正在執行的函數還調用了其餘函數,新函數會繼續被添加進入調用棧。函數執行完成,對應的棧幀當即被銷燬。vue-cli
使用 console.trace() 向 web 控制檯輸出一個堆棧跟蹤。瀏覽器
瀏覽器開發者工具進行斷點調試。服務器
棧雖然很輕量,在使用時建立,使用結束後銷燬,可是不是能夠無限增加的,被分配的調用棧空間被佔滿時,就會引發」棧溢出「的錯誤。微信
(function foo() { foo() })()
Maximum call stack size exceeded.ide
JavaScript 引擎須要用棧來維護程序執行期間的上下文的狀態,若是棧空間大了的話,全部數據都存放在棧空間裏面,會影響到上下文切換的效率,進而影響整個程序的執行效率。
堆空間存儲的數據比較複雜,大體能夠劃分爲 5 個區域:
新生代內存是臨時分配的內存,存活時間短,老生代內存是常駐內存,存活時間長。
新生代內存中的垃圾回收主要經過 Scavenge 算法進行,具體實現時主要採用 Cheney 算法。
Cheney 將內存空間一分爲二,一塊叫作 From 正在使用的內存,另外一塊叫作 To 目前閒置的內存。
Scavenge GC算法:
簡而言之,在垃圾回收的過程當中,將存活對象在兩個空間之間進行復制。
Scavenge 是典型的犧牲空間換取時間的算法,缺點是隻能使用堆內存中的一半,這是由劃分空間和複製機制所決定的。但因爲只複製存活的對象,而且對於生命週期短的場景存活對象只佔少部分,因此它在時間效率上有優異的表現。
V8 在老生代中主要採 用了 Mark-Sweep 和 Mark-Compact 相結合的方式進行垃圾回收。
當一個對象通過屢次複製依然存在時,它將會被認爲是生命週期較長的對象,這種對象會被移到老生代中,採用新的算法進行管理,這種移動稱之爲「晉級」。
對象晉級的條件主要有兩個:
已經經歷過一次 Scavenge 回收
To(閒置內存)空間的內存不足75%
標記清除,分爲標記和清除兩個階段。在標記階段遍歷堆中的全部對象,並標記活着的對象,在隨後的清除階段中,只清除沒有被標記的對象。能夠看出,Scavenge 中只複製活着的對象,而 Mark-Sweep 只清理死亡對象。
標記清除最大的問題是在進行一次標記清除回收後,內存空間會出現不連續的狀態。這種內存碎片會對後續的內存分配形成問題,由於極可能出現須要分配一個大對象的狀況,這時全部的碎片空間都沒法完成這次分配,就會提早觸發垃圾回收,而此次回收是沒必要要的。爲了解決標記清除的內存碎片問題,標記整理(Mark-Compact)被提出來。
Mark-Compact 是標記整理的意思,在 Mark-Sweep 的基礎上演變而來。
標記整理對待未存活對象不是當即回收,而是將存活對象移動到一邊,而後直接清掉端邊界之外的內存。
爲了不出現 JavaScript 應用程序與垃圾回收器看到的不一致的狀況,進行垃圾回收的時候,都須要將正在運行的程序停下來,等待垃圾回收執行完成以後再回復程序的執行,這種現象稱爲「全停頓」。若是須要回收的數據過多,那麼全停頓的時候就會比較長,會影響其餘程序的正常執行。
爲了不垃圾回收時間過長影響其餘程序的執行,V8將標記過程分紅一個個小的子標記過程,同時讓垃圾回收和JavaScript應用邏輯代碼交替執行,直到標記階段完成。咱們稱這個過程爲增量標記算法。
通俗理解,就是將本來一口氣完成的標記任務分爲了不少小的部分去完成, 每完成一個小任務就停一會, 讓 js 邏輯執行一會, 而後再繼續執行下面的部分。
從 V8 垃圾回收機制能夠看到,垃圾回收是一件很是耗時的事情, 以 1.5GB 的垃圾回收堆內存爲例,V8 作一次小的垃圾回收須要 50ms 以上,作一次非增量式的垃圾回收甚至要 1s 以上,因此要作限制。
新生代設計爲一個較小的內存空間是合理的,而老生代空間過大對於垃圾回收並沒有特別意義。V8 對內存限制的設置對於 Chrome 瀏覽器這種每一個選項卡頁面使用一個 V8 實例而言,內存的使用是綽綽有餘了。對於 Node 編寫的服務器端來講,內存限制也並不影響正常場景下的使用。可是對於 V8 的垃圾回收特色和js 在單線程上的執行狀況,垃圾回收是影響性能的因素之一。想要高性能的執行效率,須要注意讓垃圾回收儘可能少地進行,尤爲是全堆垃圾回收。
vue-cli 打包內存溢出,修改內存限制
node_modules/.bin vue-cli-service #!/usr/bin/env node --max_old_space_size=4096
調整老生代內存限制,單位mb
node --max-old-space-size=2048 build/build.js
調整新生代內存限制,單位kb
node --max-new-space-size=2048 build/build.js