V8 的內(nèi)存限制是多少,為什么 V8 這樣設(shè)計?
64 位系統(tǒng)下是 1.4GB,32 位系統(tǒng)下是 0.7GB。因為V8最初是為瀏覽器而設(shè)計的,1.4G通常普遍夠用了,而 1.5GB的垃圾回收堆內(nèi)存,V8需要 花費50 毫秒以上,做一次非增量式的垃圾回收甚至要1 秒以上。這是垃圾回收中引起Javascript線程暫停執(zhí)行的事件,在這樣的花銷下,應(yīng)用的性能和影響力都會直線下降。
但之后,增量標記會優(yōu)化這一塊的垃圾回收,所以這樣的性能問題很少再出現(xiàn)了。
js中對象的生命周期
- 分配對象內(nèi)存
- 使用內(nèi)存
- 釋放內(nèi)存
在node中采用js中使用的內(nèi)存,因為node是基于V8構(gòu)建的,所以內(nèi)存是通過V8引擎方向來進行分配和管理的。
V8 垃圾回收機制
V8在分配對象空間時,將其堆內(nèi)存分為新生代和老生代兩個區(qū)域,根據(jù)對象的存活時間長短,將對象分為新生代對象(存活時間較短的對象)和老生代對象(存活時間較長的對象),并放入到對應(yīng)的區(qū)域中。在不同的區(qū)域中采用不同的算法去進行垃圾回收
新生代垃圾回收
新生代中存放存活時間較短的對象,因為這個區(qū)域的對象要頻繁操作,考慮性能原因,設(shè)計空間就會比較小,一般64bit的大約32M左右。
在新生代中,采用Scanvege算法,將新生代的空間分為兩個大小一樣的From(也稱為對象區(qū)域)和To(空閑區(qū)域)兩個空間,From空間用來存放存活的對象,而To空間則空置。
清理過程如下:
- 將申請空間的對象放入
From空間,并將其做標記 - 在
From空間即將滿時,會觸發(fā)垃圾回收,此時將仍存活的對象,移動到To空間,并對其排序,使其不存在內(nèi)存碎片,方面后續(xù)大對象連續(xù)存儲;將From空間清空 - 將
From空間和To空間角色對換,繼續(xù)使用
這樣就能保證新生代的垃圾回收完成,還能使這兩塊空間無限重復(fù)利用下去。因為新生代的空間有限,如果一直增量下去,空間總會很快被填滿的,為此,V8采用了晉升策略,即經(jīng)過2次垃圾回收仍存活的對象,將晉升為老生代對象,會被移動到老生代區(qū)域中
老生代垃圾回收
標記-清除
老生代最初是采用標記-清除算法來進行垃圾回收的:
- 標記:從一組根元素開始,遞歸遍歷這組根元素,在遍歷過程中,能到達的對象則為存活對象,不能到達的則標記為垃圾對象(即添加垃圾標記)
- 清除:在清除階段,會將標記階段中標記的垃圾對象給清除掉
在完成垃圾回收后,老生代空間中會存在大量不連續(xù)的空間,從而導(dǎo)致可能沒有足夠的連續(xù)空間存儲大對象
標記-整理
為了解決標記-清除算法的弊端,從而就產(chǎn)生了標記-整理算法:
- 標記階段同標記-清除的第一階段不變
- 整理:在整理階段,是將沒有帶有垃圾標記的對象全給移動到內(nèi)存的一端,然后清除端邊界以外的內(nèi)存,從而保證內(nèi)存的連續(xù)存儲空間
增量標記算法
為什么要使用增量標記算法?
因為JavaScript是單線程的,而且是在主線程上運行的,一旦執(zhí)行垃圾回收,就會阻塞JavaScript腳本運行,直至垃圾回收完成后,JavaScript腳本才繼續(xù)執(zhí)行,這種因垃圾回收造成的阻塞,我們稱為 全停頓
新生代內(nèi)的空間較小,垃圾回收很快,造成的全停頓用戶基本感知不到,所以影響不大。
但老生代的空間大,對象多,垃圾回收就慢,就如我們剛開始說的,如果1.5G的內(nèi)存空間垃圾回收造成的全停頓大約在50ms以上,就會造成頁面卡頓,帶來不好的用戶體驗。
為了解決這個問題,增量標記算法就產(chǎn)生了:
-
V8將標記過程分為一個個小的子標記過程,當子標記部分完成后,立即執(zhí)行垃圾回收 - 這樣,小部分的垃圾回收和
JavaScript交替執(zhí)行,用戶就不會感知到因為垃圾回收而導(dǎo)致的卡頓了