寫過(guò)C語(yǔ)言
的都清楚,我們需要時(shí)時(shí)刻刻關(guān)心處理程序的內(nèi)存使用情況恢总,這無(wú)形的給程序員增添了很多負(fù)擔(dān)迎罗,但是在后期出現(xiàn)的一些語(yǔ)言中漸漸的都加入了內(nèi)存自動(dòng)管理和垃圾回收機(jī)制,這樣一來(lái)我們就不必再關(guān)心程序運(yùn)行的內(nèi)存使用情況片仿,同樣的在JavaScript
中也有內(nèi)存管理和垃圾回收纹安。但是這樣漸漸的內(nèi)存中的東西就離我們?cè)絹?lái)越遠(yuǎn),直至現(xiàn)在很多入門前端的對(duì)內(nèi)存的情況一概不知砂豌,我也是厢岂,當(dāng)然這是錯(cuò)誤的。
內(nèi)存分配
內(nèi)存分配的最終目的就是為了配合垃圾回收機(jī)制阳距,使得程序運(yùn)行時(shí)占用內(nèi)存更少塔粒,從而效率更高。
我們都知道數(shù)據(jù)類型有兩種:基礎(chǔ)類型和引用類型
筐摘,不同的類型采用著不同的存儲(chǔ)方式卒茬。
基礎(chǔ)類型與棧內(nèi)存
基礎(chǔ)類型值包括:undefined
、null
咖熟、boolean
圃酵、number
、string
和symbol
球恤。這些類型的值大多有固定的大小辜昵,JavaScript
將它們保存在棧內(nèi)存中直接按值引用荸镊。
棧是一種線性的數(shù)據(jù)結(jié)構(gòu)咽斧,典型特點(diǎn)是先進(jìn)后出, 后進(jìn)先出
,當(dāng)JavaScript
中一個(gè)方法執(zhí)行的時(shí)候堪置,該方法就建立一個(gè)內(nèi)存棧,然后將方法中定義的變量放入棧中张惹,當(dāng)我們需要的時(shí)候直接按值引用即可舀锨。當(dāng)方法執(zhí)行結(jié)束后就銷毀。
引用類型與堆內(nèi)存
JavaScript
的引用類型大多長(zhǎng)度不固定宛逗,比如Array
坎匿,它的長(zhǎng)度并不是固定的。他的值保存在堆內(nèi)存的對(duì)象中雷激。然后將它的地址放入棧中替蔬,而且不允許我們直接訪問(wèn)堆內(nèi)存中的位置,所以我們操作的都是對(duì)象的引用屎暇,并不是實(shí)際的對(duì)象承桥。
在程序中創(chuàng)建一個(gè)對(duì)象的成本是比較大的,在創(chuàng)建完成后就會(huì)被保存在堆數(shù)據(jù)區(qū)根悼,并不會(huì)隨著方法的結(jié)束而銷毀凶异。只有當(dāng)這個(gè)對(duì)象沒(méi)有被任何引用變量引用的時(shí)候,垃圾回收的時(shí)候才會(huì)回收掉它挤巡。
垃圾回收
JavaScript
具有自動(dòng)垃圾收集機(jī)制剩彬,執(zhí)行環(huán)境會(huì)負(fù)責(zé)找出那些不再繼續(xù)使用的變量然后釋放其占用的內(nèi)存。對(duì)于找出垃圾的方法通常有兩個(gè)策略:
標(biāo)記清除
這是最常用的垃圾收集機(jī)制矿卑。這種算法假定一個(gè)根對(duì)象喉恋,然后遍歷所有從根開(kāi)始引用的對(duì)象,垃圾收集器在運(yùn)行的時(shí)候會(huì)將他們加上標(biāo)記母廷,然后去掉環(huán)境中使用的變量和被他們引用的變量的標(biāo)記瀑晒。之后再被加上標(biāo)記的變量就是要?jiǎng)h除的,垃圾收集器將其釋放完成一次工作徘意。
引用計(jì)數(shù)
這種機(jī)制為每一個(gè)值標(biāo)記被引用的次數(shù)并追蹤苔悦,當(dāng)被其他變量引用的時(shí)候就加一,反之就減一椎咧,當(dāng)引用次數(shù)變成 0 的時(shí)候就是需要回收的了玖详。垃圾收集器下次運(yùn)行的時(shí)候就會(huì)把它釋放掉。但是這樣會(huì)有一個(gè)問(wèn)題勤讽,比如:
let obj1 = new Object();
let obj2 = new Object();
obj1.attr1 = obj2;
obj2.attr2 = obj1;
在這里obj1
和obj2
各自互相引用蟋座,這塊語(yǔ)句執(zhí)行過(guò)后他們的引用次數(shù)永遠(yuǎn)不會(huì)變成 0 ,也就得不到回收脚牍,如果存在大量這種情況的話內(nèi)存就出問(wèn)題了向臀。只要有出現(xiàn)循環(huán)引用的地方,這種機(jī)制就會(huì)出問(wèn)題诸狭。所以它無(wú)法處理循環(huán)引用的問(wèn)題券膀。
V8引擎的垃圾回收
V8 采用一種叫做分代回收
的策略君纫。將內(nèi)存分為新生代和老生代,新生代存放存活時(shí)間段的對(duì)象芹彬,老生代則存放存活時(shí)間長(zhǎng)或者常駐內(nèi)存的對(duì)象蓄髓。
大多數(shù)的對(duì)象會(huì)被分配到新生代內(nèi)存中,回收算法將這里的內(nèi)存空間一分為二舒帮,一個(gè)處于使用狀態(tài)一個(gè)處于閑置狀態(tài)会喝。分配對(duì)象的時(shí)候先把它放在使用區(qū)中,開(kāi)始垃圾回收的時(shí)候就檢查使用區(qū)中存活的對(duì)象玩郊,將他們復(fù)制到閑置區(qū)中并適當(dāng)緊縮肢执,最后釋放使用區(qū)上剩下的數(shù)據(jù)。然后閑置區(qū)變成使用區(qū)译红,使用區(qū)變成閑置區(qū)蔚万,循環(huán)往復(fù)。
當(dāng)一個(gè)對(duì)象經(jīng)過(guò)多次清理后依然存在临庇,它就會(huì)被移動(dòng)到老生代反璃,稱為
晉升
。老生代占用內(nèi)存較多假夺,主要采用
標(biāo)記清除
和標(biāo)記整理
兩個(gè)策略淮蜈。在標(biāo)記階段遍歷堆中的所有對(duì)象,標(biāo)注那些活著的對(duì)象已卷,然后在清除階段標(biāo)記清除
會(huì)清除掉沒(méi)有被標(biāo)記的對(duì)象梧田。但是這會(huì)產(chǎn)生內(nèi)存碎片。標(biāo)記清理
在清理的時(shí)候可以解決碎片問(wèn)題侧蘸,它將活著的對(duì)象向內(nèi)存中的一段移動(dòng)裁眯,然后清理掉邊界外的內(nèi)存。不過(guò)這個(gè)過(guò)程涉及到數(shù)據(jù)移動(dòng)讳癌,所以效率不是很高穿稳。
除此之外 V8 中還使用了增量標(biāo)記
,讓垃圾回收與應(yīng)用邏輯交替進(jìn)行晌坤,以減少垃圾回收時(shí)的停頓時(shí)間逢艘;在標(biāo)記完成后還可以惰性清理
;以及后期中引入了并行標(biāo)記和并行清理骤菠,通過(guò)并行來(lái)利用多核 CPU 性能它改。這些無(wú)疑讓 V8 成為了最出色的 JavaScript 引擎。