深入理解JS內(nèi)存管理機制

0

一、前言

在相當長一段時間里幽七,JS運行時的內(nèi)存問題都不被前端開發(fā)人員所關注景殷。
一方面,日常開發(fā)中基本不會遇上需要對內(nèi)存精準控制的場景澡屡,另一方面猿挚,寫JS不需要像寫 C/C++ 那樣在開發(fā)過程中隨時關注內(nèi)存的分配和釋放問題。

隨著生態(tài)的逐漸完善驶鹉,JS的執(zhí)行環(huán)境也不再局限于瀏覽器中绩蜻。目前,JS主要的執(zhí)行場景包括服務端(NodeJS室埋、Deno)办绝、桌面端(Electron)伊约、瀏覽器(ChromeMicrosoft Edge)孕蝉。

其中屡律,JS執(zhí)行引擎 V8 因其優(yōu)異的性能表現(xiàn),已經(jīng)成為主流昔驱。因此疹尾,本文對JS內(nèi)存管理模型的研究也將基于V8展開。

二骤肛、內(nèi)存結構

1

上圖展示了V8引擎的內(nèi)存結構纳本,整體上分成兩部分:

2.1 堆內(nèi)存

這里是存儲對象或動態(tài)數(shù)據(jù)的地方,也是占比最大的內(nèi)存區(qū)域腋颠。堆內(nèi)可以細分以下區(qū)域:

  • 新生代(Young generation) 新生代是新對象存在的地方繁成,這些對象中的大多數(shù)都是短暫存在的。
    這部分空間很惺缑怠(默認情況下 16~32M)巾腕,并且拆分成了兩個空間。
    空間使用 Minor GC (Scavenger) 進行垃圾回收絮蒿。
  • 老生代(Old generation) 新生代中經(jīng)歷了兩個Minor GC 周期的對象會被轉(zhuǎn)移到老生代中存放尊搬。這里占據(jù)著大量的內(nèi)存空間(默認情況下 700~1400M
    空間使用 Major GC(Mark-Sweep & Mark-Compact) 進行垃圾回收。
  • 大對象區(qū)(LARGE OBJECT SPACE) 超過一定大小的對象會直接在大對象區(qū)中被創(chuàng)建土涝,并在不被使用時將其直接回收佛寿。
  • 代碼區(qū)(CODE SPACE) 這是 即時編譯器(JIT) 存儲編譯代碼塊的地方。
  • 其他區(qū)(CELL, PROPERTY CELL,MAP SPACE) 這些空間存放大小相同的對象但壮,并且對它們指向的對象類型有一些限制冀泻。
    比如MAP SPACE里存放的是hidden class信息,這能讓V8快速定位到對象值所在的內(nèi)存區(qū)蜡饵。
2.2 棧內(nèi)存

棧是用來存儲靜態(tài)數(shù)據(jù)的地方弹渔,內(nèi)容主要包括:

  • 基本類型(Number, Boolean, String, Null, Undefined, Symbol, BigInt) 對于基本類型,系統(tǒng)會為新的變量在棧內(nèi)存中分配一個新值溯祸。
  • 引用類型 系統(tǒng)會為新的變量在棧內(nèi)存中分配一個值肢专,這個值是一個對象的引用。
  • 調(diào)用棧 解釋器創(chuàng)建了調(diào)用棧來記錄函數(shù)的調(diào)用過程焦辅。
    每調(diào)用一個函數(shù)鸟召,解釋器就把該函數(shù)添加進調(diào)用棧,解釋器會為被添加進來的函數(shù)創(chuàng)建一個棧幀(用來保存函數(shù)的局部變量以及執(zhí)行語句)并立即執(zhí)行氨鹏。
    如果正在執(zhí)行的函數(shù)還調(diào)用了其他函數(shù),新函數(shù)會繼續(xù)被添加進入調(diào)用棧压状。

三仆抵、內(nèi)存回收

棧內(nèi)存 其實是由操作系統(tǒng)進行自動管理的跟继,本文不做討論。

堆內(nèi)存 由V8進行管理镣丑,它占據(jù)最大的內(nèi)存空間舔糖,并且隨著程序運行時間的增加可能會持續(xù)增長,最終耗盡內(nèi)存莺匠。它也會變得碎片化金吗,影響程序運行的速度。這時內(nèi)存回收的重要性就體現(xiàn)出來了趣竣。

要進行內(nèi)存回收摇庙,需要先明確一個問題:什么樣的數(shù)據(jù)可以被回收。

V8通過回收 不可達對象 來釋放堆內(nèi)存遥缕,整個回收過程總體可以分為 標記回收 兩個階段卫袒,涉及到的原理是 三色標記分代回收

3.1 新生代

2.1堆內(nèi)存 小節(jié)中我們知道单匣,新生代被拆分成兩個小空間夕凝,使用 Minor GC (Scavenger) 進行垃圾回收。Minor GC (Scavenger) 我們可以簡稱為次要GC户秤。

兩個拆分出來的空間分別稱之為 to-spacefrom-space码秉。新加入的對象都會存放到from-space,當from-space被填滿時鸡号,會觸發(fā)次要GC转砖。

GC過程

  1. 標記 從堆棧指針開始遞歸遍歷 from-space 中的對象圖查找 活躍對象
  2. 復制 將這些對象復制到 to-space 中(包括被這些對象引用的所有對象)膜蠢。
    重復此操作堪藐,直到掃描 from-space 中的所有對象。另外挑围,to-space 會分配連續(xù)的內(nèi)存塊礁竞,以減少碎片。
  3. 清除 清空 from-space杉辙,因為此時剩余的對象都是可回收的模捂。
  4. 交換to-spacefrom-space 互換,即 to-space 變成 from-space蜘矢。
image

回收的最后一步是更新引用已移動的原始對象的指針狂男。每個被復制的對象都會留下一個新地址,用于更新原始指針以指向新位置品腹。

此時還存在一個問題:隨著活躍對象的累積岖食,from-space 很快會被填滿。

這時就輪到老生代出場了舞吭,在新生代中經(jīng)歷兩次GC并存活下來的對象泡垃,會被轉(zhuǎn)移到老生代析珊,這個過程被稱為晉升。如下圖:

image

至此蔑穴,新生代一次完整的垃圾回收就完成了忠寻。

3.2 老生代

在老生代中,垃圾回收為主要GC(Major GC)存和,包含了 標記清除(Mark-Sweep)標記整理(Mark-Compact)奕剃。

GC過程

  1. 標記 垃圾收集器識別哪些對象正在使用,哪些對象未使用捐腿。正在使用或可從GC根域遞歸訪問的對象被標記為活躍對象纵朋。
  2. 清除 清除未被標記為活躍對象的數(shù)據(jù)。
  3. 整理 如果碎片較多叙量,會將存活的對象移動到一起倡蝙,減少碎片提高內(nèi)存使用率。
2
3.3 三色標記

之前提到绞佩,垃圾回收時會先標記 活躍對象 來區(qū)分對象是否應該被回收寺鸥,這里就涉及到了三色標記算法。

標記位有三種顏色:

  • 白色 對象未被標記品山。
  • 灰色 對象已經(jīng)被標記胆建,但對象內(nèi)屬性還未遍歷完成。
  • 黑色 對象已經(jīng)被標記肘交,且對象內(nèi)的屬性也已完成遍歷(活躍對象)笆载。

標記過程:

  1. 開始標記
    初始所有對象都是白色,當收集器發(fā)現(xiàn)白色對象并將其推送到標記工作列表時涯呻,會將其標記成灰色凉驻。
    01.jpg
  1. 標記完成對象
    當收集器訪問目標對象的所有字段后,會將對象的顏色由灰色變?yōu)楹谏?br>
    02.jpg
  1. 標記結束
    當沒有灰色對象時复罐,代表標記結束涝登。此時剩余的白色對象表示無法訪問,可以被回收效诅。
    03.jpg

經(jīng)歷過以上三步胀滚,一次完整的標記過程就完成了。

3.4 回收優(yōu)化

現(xiàn)在我們知道乱投,一次垃圾回收總需要經(jīng)歷 標記咽笼,回收整理 等階段戚炫。

事實上剑刑,整個垃圾回收的過程是非常耗時的,比如光是標記整個堆內(nèi)存的活躍對象可能就要花費數(shù)百毫秒双肤,并且期間會阻塞程序的正常的執(zhí)行施掏。

[圖片上傳失敗...(image-ad8178-1632313567745)]

對此层宫,V8也在持續(xù)優(yōu)化,目前主要的優(yōu)化方式有:

  • 增量標記
    通過將標記任務拆分成一系列小任務其监,確保每次標記任務的持續(xù)時間在 5~10 毫秒。
    當堆的占用空間達到某個閾值大小時限匣,開始激活標記任務抖苦,此后每分配一定量的內(nèi)存,就會執(zhí)行增量標記米死。
    增量標記與常規(guī)標記一樣锌历,本質(zhì)上都是深度優(yōu)先搜索,同樣使用的是三色標記算法峦筒。

  • 并行 & 并發(fā)

    • 并行 V8會創(chuàng)建輔助線程究西,與主線程同時處理GC任務。這樣GC時間就約等于總時間除以協(xié)同的線程數(shù)了物喷。
    • 并發(fā) 這里的并發(fā)是指主線程不再處理GC任務卤材,而是由輔助線程來執(zhí)行。這樣的好處是垃圾回收不再阻塞正常任務的執(zhí)行峦失。
  • 惰性清除
    當所有對象都被標記完成扇丛,此時已經(jīng)可以進行垃圾清除的工作。但實際上這部分工作可以延遲執(zhí)行尉辑,尤其是當內(nèi)存足夠的時候帆精。
    V8會在合適的時間點執(zhí)行清除工作,比如工作線程空閑隧魄,或者內(nèi)存不足的時候卓练。

總結

內(nèi)存管理是一件非常復雜的事情,本文主要從 內(nèi)存結構內(nèi)存回收 兩個方面進行了介紹购啄,并且隱藏了其中的一些細節(jié)襟企。

在V8的內(nèi)存管理模型中,其實能學習到一些通用的內(nèi)存管理思想和性能優(yōu)化方法闸溃。
比如整吆,垃圾回收總是會圍繞標記清除辉川,整理展開表蝙。
在標記算法上,V8JAVA,Golang,PHP 等編程語言一樣乓旗,使用了三色標記府蛇。
在性能優(yōu)化上,多進程/多線程屿愚, 分步汇跨, 異步务荆, 延遲 等方式也總能發(fā)揮作用。


參考資料

原文已在玩物得志技術公眾號上發(fā)布穷遂,鏈接:https://mp.weixin.qq.com/s/7mvP5jv5sBGNfnThap6JSQ

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末函匕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蚪黑,更是在濱河造成了極大的恐慌盅惜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忌穿,死亡現(xiàn)場離奇詭異抒寂,居然都是意外死亡,警方通過查閱死者的電腦和手機掠剑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門屈芜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朴译,你說我怎么就攤上這事井佑。” “怎么了动分?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵毅糟,是天一觀的道長。 經(jīng)常有香客問我澜公,道長姆另,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任坟乾,我火速辦了婚禮迹辐,結果婚禮上,老公的妹妹穿的比我還像新娘甚侣。我一直安慰自己明吩,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布殷费。 她就那樣靜靜地躺著印荔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪详羡。 梳的紋絲不亂的頭發(fā)上仍律,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音实柠,去河邊找鬼水泉。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的草则。 我是一名探鬼主播钢拧,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炕横!你這毒婦竟也來了源内?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤份殿,失蹤者是張志新(化名)和其女友劉穎姿锭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伯铣,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年陆错,在試婚紗的時候發(fā)現(xiàn)自己被綠了啤斗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绅项。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖放前,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糯彬,我是刑警寧澤凭语,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站撩扒,受9級特大地震影響似扔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搓谆,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一炒辉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泉手,春花似錦黔寇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颊郎,卻和暖如春憋飞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袭艺。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工搀崭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓瘤睹,卻偏偏與公主長得像升敲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轰传,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容