前言
瀏覽器垃圾回收機(jī)制 GC(Garbage Collecation):垃圾收集器會(huì)定期(周期性)找出那些不在繼續(xù)使用的變量,然后釋放其內(nèi)存。
JS 回收機(jī)制
1. 標(biāo)記清除
當(dāng)變量進(jìn)入環(huán)境時(shí),將變量標(biāo)記"進(jìn)入環(huán)境",當(dāng)變量離開環(huán)境時(shí),標(biāo)記為:"離開環(huán)境"。某一個(gè)時(shí)刻,垃圾回收器會(huì)過濾掉環(huán)境中的變量,以及被環(huán)境變量引用的變量,剩下的就是被視為準(zhǔn)備回收的變量。IE、Firefox、Opera、Chrome、Safari 的 js 實(shí)現(xiàn)使用的都是標(biāo)記清除的垃圾回收策。
2. 引用計(jì)數(shù)
變量的引用次數(shù),被引用一次則加 1,當(dāng)這個(gè)引用計(jì)數(shù)為 0 時(shí),被視為準(zhǔn)備回收的對(duì)象。
瀏覽器內(nèi)存管理
GC 方案
1. 標(biāo)記內(nèi)存中的可達(dá)值
從根節(jié)點(diǎn)出發(fā),遍歷所有的對(duì)象,能訪問到的,標(biāo)記為可達(dá)的,否則為不可達(dá)。
2. 回收不可達(dá)值占據(jù)的內(nèi)存
步驟 1 完成后,清理掉不可達(dá)的對(duì)象。
3. 內(nèi)存整理
步驟 2 完成后,進(jìn)行內(nèi)存碎片整理。
GC 不足
瀏覽器進(jìn)行垃圾回收的時(shí)候,會(huì)暫停 JavaScript 腳本,等垃圾回收完畢再繼續(xù)執(zhí)行其他代碼。對(duì)于 JS 游戲、動(dòng)畫對(duì)連貫性要求比較高的應(yīng)用,如果暫停時(shí)間很長(zhǎng)就會(huì)造成頁(yè)面卡頓。
GC 優(yōu)化策略
1. 分代收集
多回收新生代,少回收老生代。

2. 增量收集
將垃圾收集工作分成更小的塊,每次處理一部分,多次處理。

3. 閑時(shí)收集
在 CPU 空閑時(shí)運(yùn)行,減少對(duì)代碼的影響。
內(nèi)存泄露
內(nèi)存泄露是指一塊被分配的內(nèi)存既不能使用,又不能回收,直到瀏覽器進(jìn)程結(jié)束
常見內(nèi)存泄漏情況:
- 全局變量
- 閉包
- DOM 元素引用
- 定時(shí)器、回調(diào)
- 子元素存在引用
- 監(jiān)聽事件沒有解綁
V8 引擎 GC
V8 的 GC 策略主要基于分代式垃圾回收機(jī)制,根據(jù)對(duì)象的存活時(shí)間進(jìn)行不同的分代,分別采用不同的垃圾回收算法。
1. V8 的內(nèi)存結(jié)構(gòu)
垃圾回收的過程主要出現(xiàn)在新生代和老生代
帶斜紋的區(qū)域代表暫未使用的內(nèi)存:

①. 新生代(new_space)
新生代主要用于存放存活時(shí)間較短的對(duì)象,其兩個(gè) semispace(半空間)構(gòu)成的,內(nèi)存最大值在 64 位系統(tǒng)和 32 位系統(tǒng)上分別為 32MB 和 16MB,在新生代的垃圾回收過程中主要采用了 Scavenge 算法。
(1). Scavenge 算法
Scavenge 算法是一種典型的犧牲空間換取時(shí)間的算法,兩個(gè)空間中,始終只有一個(gè)處于使用狀態(tài),另一個(gè)處于閑置狀態(tài)。當(dāng)進(jìn)行垃圾回收時(shí):
如果 From 空間中尚有存活對(duì)象,則會(huì)被復(fù)制到 To 空間進(jìn)行保存,非存活的對(duì)象會(huì)被自動(dòng)回收。
當(dāng)復(fù)制完成后,F(xiàn)rom 空間和 To 空間完成一次角色互換,To 空間會(huì)變?yōu)樾碌?From 空間,原來(lái)的 From 空間則變?yōu)?To 空間。
Ⅰ. From 空間中分配了三個(gè)對(duì)象 A、B、C

Ⅱ. 當(dāng)程序主線程任務(wù)第一次執(zhí)行完畢后進(jìn)入垃圾回收時(shí),發(fā)現(xiàn)對(duì)象 A 已經(jīng)沒有其他引用,則表示可以對(duì)其進(jìn)行回收

Ⅲ. 對(duì)象 B 和對(duì)象 C 此時(shí)依舊處于活躍狀態(tài),因此會(huì)被復(fù)制到 To 空間中進(jìn)行保存

Ⅳ. 接下來(lái)將 From 空間中的所有非存活對(duì)象全部清除

Ⅴ. 此時(shí) From 空間中的內(nèi)存已經(jīng)清空,開始和 To 空間完成一次角色互換

Ⅵ. 當(dāng)程序主線程在執(zhí)行第二個(gè)任務(wù)時(shí),在 From 空間中分配了一個(gè)新對(duì)象 D

Ⅶ. 任務(wù)執(zhí)行完畢后再次進(jìn)入垃圾回收,發(fā)現(xiàn)對(duì)象 D 已經(jīng)沒有其他引用,表示可以對(duì)其進(jìn)行回收

Ⅷ. 對(duì)象 B 和對(duì)象 C 此時(shí)依舊處于活躍狀態(tài),再次被復(fù)制到 To 空間中進(jìn)行保存

Ⅸ. 再次將 From 空間中的所有非存活對(duì)象全部清除

Ⅹ. From 空間和 To 空間繼續(xù)完成一次角色互換

Scavenge 算法過程:將存活對(duì)象在 From 空間和 To 空間之間進(jìn)行復(fù)制,同時(shí)完成兩個(gè)空間之間的角色互換,其缺點(diǎn)是:浪費(fèi)了一半的內(nèi)存用于復(fù)制。
(2). 對(duì)象晉升
當(dāng)一個(gè)對(duì)象在經(jīng)過多次復(fù)制之后依舊存活,那么它會(huì)被認(rèn)為是一個(gè)生命周期較長(zhǎng)的對(duì)象,在下一次進(jìn)行垃圾回收時(shí),該對(duì)象會(huì)被直接轉(zhuǎn)移到老生代中。
晉升的條件:
經(jīng)歷過一次 Scavenge 算法
To 空間的內(nèi)存占比超過 25%
②. 老生代(old_space)
老生代中管理著大量的存活對(duì)象,采用 Mark-Sweep(標(biāo)記清除)和 Mark-Compact(標(biāo)記整理)來(lái)進(jìn)行管理內(nèi)存。
(1). Mark-Sweep 算法
Mark-Sweep(標(biāo)記清除)分為標(biāo)記和清除兩個(gè)階段,
- 標(biāo)記階段,遍歷堆中的所有對(duì)象,然后標(biāo)記活著的對(duì)象
- 清除階段,將死亡的對(duì)象進(jìn)行清除
(2). 根節(jié)點(diǎn)
- 全局對(duì)象
- 本地函數(shù)的局部變量和參數(shù)
- 當(dāng)前嵌套調(diào)用鏈上的其他函數(shù)的變量和參數(shù)
③. 大對(duì)象區(qū)(large_object_space)
存放體積超越其他區(qū)域大小的對(duì)象,每個(gè)對(duì)象都會(huì)有自己的內(nèi)存,垃圾回收不會(huì)移動(dòng)大對(duì)象區(qū)。
④. 代碼區(qū)(code_space)
代碼對(duì)象,會(huì)被分配在這里,唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū)域。
⑤. map 區(qū)(map_space)
存放 Cell 和 Map,每個(gè)區(qū)域都是存放相同大小的元素。
參考
END