寫過C語言
的都清楚,咱們須要時時刻刻關心處理程序的內存使用狀況,這無形的給程序員增添了不少負擔,可是在後期出現的一些語言中漸漸的都加入了內存自動管理和垃圾回收機制,這樣一來咱們就沒必要再關心程序運行的內存使用狀況,一樣的在JavaScript
中也有內存管理和垃圾回收。可是這樣漸漸的內存中的東西就離咱們愈來愈遠,直至如今不少入門前端的對內存的狀況一律不知,我也是,固然這是錯誤的。javascript
內存分配的最終目的就是爲了配合垃圾回收機制,使得程序運行時佔用內存更少,從而效率更高。前端
咱們都知道數據類型有兩種:基礎類型和引用類型
,不一樣的類型採用着不一樣的存儲方式。java
基礎類型值包括:undefined
、null
、boolean
、number
、string
和symbol
。這些類型的值大多有固定的大小,JavaScript
將它們保存在棧內存中直接按值引用。 棧是一種線性的數據結構,典型特色是先進後出, 後進先出
,當JavaScript
中一個方法執行的時候,該方法就創建一個內存棧,而後將方法中定義的變量放入棧中,當咱們須要的時候直接按值引用便可。當方法執行結束後就銷燬。程序員
JavaScript
的引用類型大多長度不固定,好比Array
,它的長度並非固定的。他的值保存在堆內存的對象中。而後將它的地址放入棧中,並且不容許咱們直接訪問堆內存中的位置,因此咱們操做的都是對象的引用,並非實際的對象。 在程序中建立一個對象的成本是比較大的,在建立完成後就會被保存在堆數據區,並不會隨着方法的結束而銷燬。只有當這個對象沒有被任何引用變量引用的時候,垃圾回收的時候纔會回收掉它。算法
JavaScript
具備自動垃圾收集機制,執行環境會負責找出那些再也不繼續使用的變量而後釋放其佔用的內存。對於找出垃圾的方法一般有兩個策略:數據結構
這是最經常使用的垃圾收集機制。這種算法假定一個根對象,而後遍歷全部從根開始引用的對象,垃圾收集器在運行的時候會將他們加上標記,而後去掉環境中使用的變量和被他們引用的變量的標記。以後再被加上標記的變量就是要刪除的,垃圾收集器將其釋放完成一次工做。性能
這種機制爲每個值標記被引用的次數並追蹤,當被其餘變量引用的時候就加一,反之就減一,當引用次數變成 0 的時候就是須要回收的了。垃圾收集器下次運行的時候就會把它釋放掉。可是這樣會有一個問題,好比:ui
let obj1 = new Object();
let obj2 = new Object();
obj1.attr1 = obj2;
obj2.attr2 = obj1;
複製代碼
在這裏obj1
和obj2
各自互相引用,這塊語句執行事後他們的引用次數永遠不會變成 0 ,也就得不到回收,若是存在大量這種狀況的話內存就出問題了。只要有出現循環引用的地方,這種機制就會出問題。因此它沒法處理循環引用的問題。spa
V8 採用一種叫作分代回收
的策略。將內存分爲新生代和老生代,新生代存放存活時間段的對象,老生代則存放存活時間長或者常駐內存的對象。code
大多數的對象會被分配到新生代內存中,回收算法將這裏的內存空間一分爲二,一個處於使用狀態一個處於閒置狀態。分配對象的時候先把它放在使用區中,開始垃圾回收的時候就檢查使用區中存活的對象,將他們複製到閒置區中並適當緊縮,最後釋放使用區上剩下的數據。而後閒置區變成使用區,使用區變成閒置區,循環往復。
當一個對象通過屢次清理後依然存在,它就會被移動到老生代,稱爲晉升
。
老生代佔用內存較多,主要採用標記清除
和標記整理
兩個策略。在標記階段遍歷堆中的全部對象,標註那些活着的對象,而後在清除階段標記清除
會清除掉沒有被標記的對象。可是這會產生內存碎片。標記清理
在清理的時候能夠解決碎片問題,它將活着的對象向內存中的一段移動,而後清理掉邊界外的內存。不過這個過程涉及到數據移動,因此效率不是很高。
除此以外 V8 中還使用了增量標記
,讓垃圾回收與應用邏輯交替進行,以減小垃圾回收時的停頓時間;在標記完成後還能夠惰性清理
;以及後期中引入了並行標記和並行清理,經過並行來利用多核 CPU 性能。這些無疑讓 V8 成爲了最出色的 JavaScript 引擎。