亚洲乱色熟女一区二区三区丝袜,天堂√中文最新版在线,亚洲精品乱码久久久久久蜜桃图片,香蕉久久久久久av成人,欧美丰满熟妇bbb久久久

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

V8 引擎是如何給 JS"打掃房間"的 ?

freeflydom
2025年7月10日 9:53 本文熱度 774

JS 語(yǔ)言不像 C/C++, 讓程序員自己去開(kāi)辟或者釋放內(nèi)存,而是類(lèi)似Java,采用自己的一套垃圾回收算法進(jìn)行自動(dòng)的內(nèi)存管理。今天就從內(nèi)存結(jié)構(gòu)說(shuō)起,一步步聊聊 V8 的垃圾回收機(jī)制。

先搞懂JS 的內(nèi)存都存在哪里?

JS 的內(nèi)存存儲(chǔ)分兩塊:棧(Stack)  和堆(Heap) ,就像家里的 "鞋柜" 和 "儲(chǔ)物間"—— 常用的小東西放鞋柜,大件雜物放儲(chǔ)物間。

棧是一塊連續(xù)的內(nèi)存空間,就像排隊(duì)的抽屜,每個(gè)抽屜大小固定。它主要存兩種東西:

  • 基本類(lèi)型值(Number、String、Boolean 等),比如let age = 25,age和 25 都存在棧里;
  • 引用類(lèi)型的地址(指針),比如let user = {name: '張三'},user這個(gè)變量名存在棧里,而{name: '張三'}這個(gè)對(duì)象存在堆里,棧里的user只存了指向堆中對(duì)象的地址。
  • 閉包是存儲(chǔ)在堆內(nèi)存中的

棧的回收特別簡(jiǎn)單:函數(shù)執(zhí)行時(shí)會(huì)創(chuàng)建 "執(zhí)行上下文",壓入棧頂;函數(shù)執(zhí)行完,執(zhí)行上下文彈出,棧頂?shù)膬?nèi)存會(huì)被自動(dòng)回收。這個(gè)過(guò)程由 JS 引擎自動(dòng)完成,靠的是ESP 指針(棧指針)的移動(dòng):函數(shù)執(zhí)行時(shí) ESP 上移,執(zhí)行完 ESP 下移,原來(lái)的棧空間就成了 "可回收區(qū)域"。

堆是一塊不連續(xù)的內(nèi)存空間,大小不固定,就像開(kāi)放式儲(chǔ)物間,專(zhuān)門(mén)存引用類(lèi)型(對(duì)象、數(shù)組、函數(shù)等)。比如創(chuàng)建一個(gè){name: '張三', age: 25}對(duì)象,它的鍵值對(duì)全存在堆里;數(shù)組[1, 2, 3]的元素也存在堆里。

堆的麻煩在于:對(duì)象不會(huì)像棧里的變量那樣 "用完就走"。比如全局對(duì)象window.appData = { ... },只要頁(yè)面不刷新,它就一直占著堆內(nèi)存;再比如閉包中被引用的對(duì)象,即使函數(shù)執(zhí)行完,只要還被引用,就不會(huì)被回收。這些不再被引用的對(duì)象,就是堆里的 "垃圾"—— 如果不及時(shí)清理,堆內(nèi)存會(huì)被越占越多,最終導(dǎo)致頁(yè)面卡頓甚至崩潰。

V8 的內(nèi)存結(jié)構(gòu)

V8 引擎把堆內(nèi)存又細(xì)分了兩塊:

  • 新生代內(nèi)存:臨時(shí)貨架,存存活時(shí)間短的對(duì)象(比如函數(shù)里的局部對(duì)象、循環(huán)中創(chuàng)建的臨時(shí)變量)。64 位系統(tǒng)下約 32MB,32 位系統(tǒng)約 16MB,空間不大但回收頻繁。
  • 老生代內(nèi)存:長(zhǎng)期儲(chǔ)物柜,存存活時(shí)間長(zhǎng)的對(duì)象(比如全局變量、被多次回收仍存在的對(duì)象)。64 位系統(tǒng)下最大約 1.4GB,32 位系統(tǒng)約 0.7GB,空間大但回收頻率低。

這就是 V8 的 "分代回收" 思路:不同生命周期的對(duì)象,用不同的方式回收,效率更高。

棧內(nèi)存的回收

棧的回收幾乎不用我們操心,全靠 JS 引擎的 "執(zhí)行上下文管理"。舉個(gè)例子:

function add(a, b) {
  let sum = a + b; // sum存在棧里
  return sum;
}
let result = add(1, 2); // add執(zhí)行時(shí),上下文入棧;執(zhí)行完,上下文出棧
  • 調(diào)用add(1, 2)時(shí),JS 引擎會(huì)創(chuàng)建一個(gè)執(zhí)行上下文,壓入棧頂,里面包含a=1、b=2、sum=3這些變量。
  • 函數(shù)返回后,執(zhí)行上下文從棧頂彈出,ESP 指針下移到上一個(gè)上下文(全局上下文)。此時(shí)a、bsum占用的??臻g就成了 "無(wú)效區(qū)域",下次有新函數(shù)調(diào)用時(shí),直接覆蓋這些空間就行 —— 不用專(zhuān)門(mén) "清理",相當(dāng)于自動(dòng)回收。

這種回收方式效率極高,幾乎不消耗額外性能,所以棧內(nèi)存很少出問(wèn)題。

新生代內(nèi)存的回收

新生代存的都是 "短命對(duì)象",比如循環(huán)里創(chuàng)建的臨時(shí)對(duì)象:

for (let i = 0; i < 1000; i++) {
  const temp = { id: i }; // 每次循環(huán)創(chuàng)建的temp對(duì)象,用完就成垃圾
  console.log(temp.id);
}

這些對(duì)象的回收,V8 用的是Scavenge 算法,核心是 "復(fù)制存活對(duì)象,清空剩余空間"。具體步驟可以腦補(bǔ)成這樣:

  1. 新生代內(nèi)存被分成兩塊等大的空間,一塊叫 "From 空間"(正在用),一塊叫 "To 空間"(閑置),就像兩個(gè)并排的抽屜,每次只用一個(gè)。

  1. 當(dāng) From 空間快滿(mǎn)時(shí),觸發(fā)回收:遍歷 From 空間,把所有還在被引用的 "存活對(duì)象",按順序復(fù)制到 To 空間。
  2. 復(fù)制完后,直接清空 From 空間(把舊抽屜里的垃圾全扔了),然后交換 From 和 To 的角色 —— 下次用新的 From(原來(lái)的 To),閑置新的 To(原來(lái)的 From)。

為什么要這么折騰?主要是為了避免 "內(nèi)存碎片"。如果直接刪除垃圾,存活對(duì)象可能零散分布在內(nèi)存中,就像抽屜里的雜物東一個(gè)西一個(gè),下次想放個(gè)大點(diǎn)的對(duì)象(比如一個(gè)長(zhǎng)數(shù)組),可能找不到連續(xù)的空間。

而復(fù)制到 To 空間時(shí)按順序排列,存活對(duì)象會(huì)擠在一起,剩下的空間是一整塊連續(xù)區(qū)域,下次分配新對(duì)象就很方便。

當(dāng)然,這種方式也有代價(jià):新生代內(nèi)存實(shí)際只能用一半(總有一個(gè)空間閑置)。但好在新生代對(duì)象存活時(shí)間短,復(fù)制成本低,總體算下來(lái)比標(biāo)記清除快得多。

老生代內(nèi)存的回收

當(dāng)新生代的對(duì)象 "活過(guò)" 多次回收(比如被全局變量引用,或者在閉包里被長(zhǎng)期持有),就會(huì)被 "晉升" 到老生代。 晉升的條件有兩個(gè)

  • 已經(jīng)經(jīng)歷過(guò)一次 Scavenge 回收。
  • To(閑置)空間的內(nèi)存占用超過(guò)25%。

老生代的對(duì)象要么體積大,要么存活久,用 Scavenge 算法復(fù)制太費(fèi)時(shí)間,所以 V8 換了套思路:標(biāo)記 - 清除+標(biāo)記 - 整理。

第一步:標(biāo)記 - 清除

  1. 標(biāo)記階段:從全局對(duì)象(比如window)開(kāi)始,遍歷所有能訪(fǎng)問(wèn)到的對(duì)象,給它們貼個(gè) "有用" 的標(biāo)簽(可達(dá)性分析)。
  2. 清除階段:遍歷完后,沒(méi)貼標(biāo)簽的對(duì)象就是 "垃圾",直接釋放它們的內(nèi)存。

這種方式解決了引用計(jì)數(shù)法的 "循環(huán)引用" 問(wèn)題。比如兩個(gè)對(duì)象互相引用,但都不再被全局訪(fǎng)問(wèn),標(biāo)記階段它們不會(huì)被標(biāo)記,清除階段會(huì)被回收 —— 而引用計(jì)數(shù)法會(huì)因?yàn)樗鼈兓ハ嘁?,?jì)數(shù)不為 0,永遠(yuǎn)不回收,導(dǎo)致內(nèi)存泄漏。

第二步:標(biāo)記 - 整理

標(biāo)記 - 清除后,堆內(nèi)存會(huì)像被挖過(guò)的地一樣坑坑洼洼:存活對(duì)象零散分布,中間夾雜著被回收的空白區(qū)域(內(nèi)存碎片)。下次想分配一個(gè)大對(duì)象,可能找不到連續(xù)的空間,明明總內(nèi)存夠,卻分配失敗。

所以 V8 會(huì)緊接著做 "標(biāo)記 - 整理":把所有存活對(duì)象往內(nèi)存的一端 "擠",讓空白區(qū)域集中到另一端,形成一整塊連續(xù)的空閑內(nèi)存。就像把衣柜里的衣服都推到左邊,右邊留出一大塊空地放新衣服。

老生代的"增量標(biāo)記"

JS 是單線(xiàn)程的,一旦開(kāi)始垃圾回收,JS 代碼就會(huì)暫停(稱(chēng)為 "Stop-The-World")。如果老生代內(nèi)存很大,一次完整的標(biāo)記 - 清除可能要卡 1 秒以上 —— 用戶(hù)點(diǎn)按鈕沒(méi)反應(yīng),頁(yè)面像死機(jī)了一樣。

為了解決這個(gè)問(wèn)題,V8 用了增量標(biāo)記:把原本一口氣完成的標(biāo)記階段,拆成一小塊一小塊,穿插在 JS 代碼執(zhí)行間隙。比如標(biāo)記 10ms,就讓 JS 執(zhí)行 20ms,再標(biāo)記 10ms... ,如果循環(huán),直到標(biāo)記階段完成才進(jìn)入內(nèi)存碎片的整理上面來(lái),不耽誤JS代碼的正常執(zhí)行。

這樣一來(lái),單次垃圾回收的阻塞時(shí)間從幾百毫秒降到幾十毫秒,用戶(hù)幾乎感覺(jué)不到卡頓。數(shù)據(jù)顯示,增量標(biāo)記能把垃圾回收的阻塞時(shí)間減少到原來(lái)的 1/6,對(duì)大型應(yīng)用來(lái)說(shuō)太重要了。

最后

搞懂 V8 的回收機(jī)制后,我總結(jié)了幾個(gè)對(duì)開(kāi)發(fā)有用的點(diǎn):

  1. 少創(chuàng)建臨時(shí)對(duì)象:循環(huán)、頻繁調(diào)用的函數(shù)里,盡量復(fù)用對(duì)象(比如把const obj = { ... }提到循環(huán)外),減少新生代回收壓力。
  2. 及時(shí)解除引用:不用的全局變量、定時(shí)器、事件監(jiān)聽(tīng),記得設(shè)為null,讓對(duì)象失去引用,被及時(shí)回收。
  3. 小心閉包陷阱:閉包會(huì)讓內(nèi)部對(duì)象被長(zhǎng)期引用,比如function create() { const data = bigObject; return () => data; },data會(huì)一直存在老生代,不用時(shí)要手動(dòng)解除引用。

?轉(zhuǎn)自https://juejin.cn/post/7524812060761489418


該文章在 2025/7/10 9:53:21 編輯過(guò)
相關(guān)文章
正在查詢(xún)...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專(zhuān)業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類(lèi)企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶(hù)的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved