自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Javascript的垃圾回收機(jī)制知多少?

開(kāi)發(fā) 前端
本文主要圍繞JS引擎相關(guān)知識(shí),來(lái)深入了解底層運(yùn)行邏輯,這對(duì)于日常開(kāi)發(fā)維護(hù)高性能Javascript代碼以及排查代碼性能問(wèn)題有著很好的幫助。

[[438972]]

1 寫(xiě)在前面

本文主要圍繞JS引擎相關(guān)知識(shí),來(lái)深入了解底層運(yùn)行邏輯,這對(duì)于日常開(kāi)發(fā)維護(hù)高性能Javascript代碼以及排查代碼性能問(wèn)題有著很好的幫助。關(guān)于JS引擎底層的垃圾回收機(jī)制,后面才能理解內(nèi)存泄漏的問(wèn)題以及手動(dòng)預(yù)防和優(yōu)化,實(shí)現(xiàn)對(duì)JS內(nèi)存管理以及內(nèi)存溢出的處理。

  • 那么我們需要考慮幾個(gè)問(wèn)題:
  • 什么是垃圾回收機(jī)制(GC)?
  • 垃圾是怎樣產(chǎn)生的?
  • 為什么要進(jìn)行垃圾回收?
  • Javascript的內(nèi)存是如何管理的?
  • Chrome瀏覽器又是如何進(jìn)行垃圾回收的?

2 內(nèi)存管理

在Javascript編程中,內(nèi)存管理大概分成三個(gè)步驟,也是內(nèi)存的生命周期:

  • 分配你所需系統(tǒng)內(nèi)存的空間
  • 使用分配到的內(nèi)存進(jìn)行讀寫(xiě)操作
  • 不需要使用內(nèi)存時(shí),將空間進(jìn)行釋放和歸還

內(nèi)存的生命周期

與其它手動(dòng)管理內(nèi)存的語(yǔ)言不一樣的是,在Javascript中,當(dāng)我們創(chuàng)建變量時(shí),系統(tǒng)會(huì)給對(duì)象進(jìn)行自動(dòng)分配對(duì)應(yīng)的內(nèi)存空間以及閑置資源回收,也就是不需要我們手動(dòng)進(jìn)行分配。但是,正是因?yàn)槔厥諜C(jī)制導(dǎo)致開(kāi)發(fā)者有著錯(cuò)誤的感覺(jué),就是他們不用關(guān)心內(nèi)存管理。

  1. const name = "yichuan";//給字符串分配棧內(nèi)存 
  2. const age = 18;//給數(shù)值分配棧內(nèi)存 
  3.  
  4. //給對(duì)象以及包含的值分配堆內(nèi)存 
  5. const user = { 
  6.   name"onechuan"
  7.   age: 19 
  8. //給數(shù)組以及包含的值分配堆內(nèi)存 
  9. const arr = ["yichuan","onechuan",18]; 
  10. //給函數(shù)對(duì)象分配堆內(nèi)存 
  11. function sum(x,y){ 
  12.   return x + y; 

在前面《Javascript的數(shù)據(jù)類(lèi)型知多少》文中,我們知道了基礎(chǔ)數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型的分配機(jī)制,即:

  • 簡(jiǎn)單數(shù)據(jù)類(lèi)型內(nèi)存保存在固定的??臻g中,可直接通過(guò)值進(jìn)行訪問(wèn)
  • 引用數(shù)據(jù)類(lèi)型的值大小不固定,其引用地址保存在??臻g、引用所指向的值保存在堆空間中,需要通過(guò)引用進(jìn)行訪問(wèn)

棧內(nèi)存中的基本數(shù)據(jù)類(lèi)型,可以直接通過(guò)操作系統(tǒng)進(jìn)行處理,而堆內(nèi)存中的引用數(shù)據(jù)類(lèi)型的值大小不確定,因此需要JS的引擎通過(guò)垃圾回收機(jī)制進(jìn)行處理。

3 內(nèi)存回收機(jī)制(GC)

Javascript的V8引擎被限制了內(nèi)存的使用,因此根據(jù)不同操作系統(tǒng)的內(nèi)存大小會(huì)不一樣。

V8引擎最初設(shè)計(jì)是作為瀏覽器的引擎,并未考慮占據(jù)過(guò)多的內(nèi)存空間,隨著web技術(shù)工程化的發(fā)展,占據(jù)了越來(lái)越多的內(nèi)存空間。又由于被v8的會(huì)回收機(jī)制所限制,這樣就引起了js執(zhí)行的線程被掛起,會(huì)影響當(dāng)前執(zhí)行的頁(yè)面應(yīng)用性能。

垃圾回收算法:就是垃圾收集器按照固定的時(shí)間間隔,周期性地尋找那些不再使用的變量,然后將其清楚或釋放內(nèi)存。但是垃圾回收算法是個(gè)不完美的方案,因?yàn)槟硥K內(nèi)存是否還可用,屬于不可預(yù)判的問(wèn)題,也就意味著單純依靠算法是解決不了的。還有為什么不是實(shí)時(shí)的找出無(wú)用內(nèi)存并釋放呢?其實(shí)很簡(jiǎn)單,實(shí)時(shí)開(kāi)銷(xiāo)太大了。

我們知道了垃圾是如何產(chǎn)生的,那么我們應(yīng)該如何清除呢?在瀏覽器的發(fā)展歷史上有兩種解決策略:

  • 標(biāo)記清除
  • 引用計(jì)數(shù)

標(biāo)記清除

標(biāo)記清除分為:標(biāo)記階段和清除階段。

首先它會(huì)遍歷堆內(nèi)存上所有的對(duì)象,分別給它們打上標(biāo)記,然后在代碼執(zhí)行過(guò)程結(jié)束之后,對(duì)所使用過(guò)的變量取消標(biāo)記。在清除階段再把具有標(biāo)記的內(nèi)存對(duì)象進(jìn)行整體清除,從而釋放內(nèi)存空間。

整個(gè)標(biāo)記清除算法大致過(guò)程就像下面這樣

  • 垃圾收集器在運(yùn)行時(shí)會(huì)給內(nèi)存中的所有變量都加上一個(gè)標(biāo)記
  • 然后從各個(gè)根對(duì)象開(kāi)始遍歷,把還在被上下文變量引用的變量標(biāo)記去掉標(biāo)記
  • 清理所有帶有標(biāo)牌機(jī)的變量,銷(xiāo)毀并回收它們所占用的內(nèi)存空間
  • 最后垃圾回收程序做一次內(nèi)存清理

使用標(biāo)記清除策略的最重要的優(yōu)點(diǎn)在于簡(jiǎn)單,無(wú)非是標(biāo)記和不標(biāo)記的差異。通過(guò)標(biāo)記清除之后,剩余的對(duì)象內(nèi)存位置是不變的,也會(huì)導(dǎo)致空閑內(nèi)存空間是不連續(xù)的,這就造成出現(xiàn)內(nèi)存碎片的問(wèn)題。內(nèi)存碎片多了后,如果要存儲(chǔ)一個(gè)新的需要占據(jù)較大內(nèi)存空間的對(duì)象,就會(huì)造成影響。對(duì)于通過(guò)標(biāo)記清除產(chǎn)生的內(nèi)存碎片,還是需要通過(guò)標(biāo)記整理策略進(jìn)行解決。

簡(jiǎn)而言之:

  • 優(yōu)點(diǎn):簡(jiǎn)單
  • 缺點(diǎn):內(nèi)存碎片化、分配速度慢

標(biāo)記整理

經(jīng)過(guò)標(biāo)記清除策略整理后,老生代內(nèi)存中因此產(chǎn)生了許多內(nèi)存碎片,如果不進(jìn)行清理內(nèi)存碎片,就會(huì)對(duì)存儲(chǔ)造成影響。

標(biāo)記整理(Mark-Compact)算法 就可以有效地解決標(biāo)記清除的兩個(gè)缺點(diǎn)。它的標(biāo)記階段和標(biāo)記清除算法沒(méi)有什么不同,只是標(biāo)記結(jié)束后,標(biāo)記整理算法會(huì)將活著的對(duì)象(即不需要清理的對(duì)象)向內(nèi)存的一端移動(dòng),最后清理掉邊界的內(nèi)存。

引用計(jì)數(shù)

引用計(jì)數(shù)是一種不常見(jiàn)的垃圾回收策略,其思路就是對(duì)每個(gè)值都記錄其的引用次數(shù)。具體的:

  • 當(dāng)變量進(jìn)行聲明并賦值后,值的引用數(shù)為1。
  • 當(dāng)同一個(gè)值被賦值給另一個(gè)變量時(shí),引用數(shù)+1
  • 當(dāng)保存該值引用的變量被其它值覆蓋時(shí),引用數(shù)-1
  • 當(dāng)該值的引用數(shù)為0時(shí),表示無(wú)法再訪問(wèn)該值了,此時(shí)就可以放心地將其清除并回收內(nèi)存。
  1. let a = new Object()  // 此對(duì)象的引用計(jì)數(shù)為 1(a引用) 
  2. let b = a   // 此對(duì)象的引用計(jì)數(shù)是 2(a,b引用) 
  3. a = null    // 此對(duì)象的引用計(jì)數(shù)為 1(b引用) 
  4. b = null    // 此對(duì)象的引用計(jì)數(shù)為 0(無(wú)引用) 
  5. ...   // GC 回收此對(duì)象 

這種回收策略看起來(lái)很方便,但是當(dāng)其進(jìn)行循環(huán)引用時(shí)就會(huì)出現(xiàn)問(wèn)題,會(huì)造成大量的內(nèi)存不會(huì)被釋放。當(dāng)函數(shù)結(jié)束后,兩個(gè)對(duì)象都不在作用域中,A 和 B 都會(huì)被當(dāng)作非活動(dòng)對(duì)象來(lái)清除掉,相比之下,引用計(jì)數(shù)則不會(huì)釋放,也就會(huì)造成大量無(wú)用內(nèi)存占用,這也是后來(lái)放棄引用計(jì)數(shù),使用標(biāo)記清除的原因之一。

4 V8對(duì)于垃圾回收機(jī)制的優(yōu)化

大多數(shù)瀏覽器都是基于標(biāo)記清除算法,不同的只是在運(yùn)行垃圾回收的頻率具有差異。V8 對(duì)其進(jìn)行了一些優(yōu)化加工處理,那接下來(lái)我們主要就來(lái)看 V8 中對(duì)垃圾回收機(jī)制的優(yōu)化。

分代式垃圾回收

V8 的垃圾回收策略主要基于分代式垃圾回收機(jī)制,V8 中將堆內(nèi)存分為新生代和老生代兩區(qū)域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收。

新生代的對(duì)象為存活時(shí)間較短的對(duì)象,簡(jiǎn)單來(lái)說(shuō)就是新產(chǎn)生的對(duì)象,通常只支持 1~8M 的容量,而老生代的對(duì)象為存活事件較長(zhǎng)或常駐內(nèi)存的對(duì)象,簡(jiǎn)單來(lái)說(shuō)就是經(jīng)歷過(guò)新生代垃圾回收后還存活下來(lái)的對(duì)象,容量通常比較大。

V8 整個(gè)堆內(nèi)存的大小就等于新生代加上老生代的內(nèi)存,對(duì)于新老兩塊內(nèi)存區(qū)域的垃圾回收,V8 采用了兩個(gè)垃圾回收器來(lái)管控。

新生代和老生代

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

在64操作系統(tǒng)下分配為32MB,因?yàn)樾律械淖兞看婊顣r(shí)間短,不太容易產(chǎn)生太大的內(nèi)存壓力,因此不夠大也是能夠理解。

對(duì)于新生代內(nèi)存的回收,通常是通過(guò)Scavenge 的算法進(jìn)行垃圾回收,就是將新生代內(nèi)存進(jìn)行一分為二,正在被使用的內(nèi)存空間稱為使用區(qū),而限制狀態(tài)的內(nèi)存空間稱為空閑區(qū)。

新生代內(nèi)存回收的原理是:

  • 新加入的對(duì)象都會(huì)存放在使用區(qū),當(dāng)使用區(qū)快寫(xiě)滿時(shí)就進(jìn)行一次垃圾清理操作。
  • 在開(kāi)始進(jìn)行垃圾回收時(shí),新生代回收器會(huì)對(duì)使用區(qū)內(nèi)的對(duì)象進(jìn)行標(biāo)記
  • 標(biāo)記完成后,需要對(duì)使用區(qū)內(nèi)的活動(dòng)對(duì)象拷貝到空閑區(qū)進(jìn)行排序
  • 而后進(jìn)入垃圾清理階段,將非活動(dòng)對(duì)象占用的內(nèi)存空間進(jìn)行清理
  • 最后對(duì)使用區(qū)和空閑區(qū)進(jìn)行交換,使用區(qū)->空閑區(qū),空閑區(qū)->使用區(qū)

新生代中的變量如果經(jīng)過(guò)回收之后依然一直存在,那么會(huì)放入到老生代內(nèi)存中,只要是已經(jīng)經(jīng)歷過(guò)一次Scavenge算法回收的,就可以晉升為老生代內(nèi)存的對(duì)象。

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

當(dāng)然,Scavenge算法也有其適用場(chǎng)景范圍,對(duì)于內(nèi)存空間較大的就不適合使用Scavenge算法。此時(shí)應(yīng)該使用Mark-Sweep(標(biāo)記清除)和Mark-Compact(標(biāo)記整理)的策略進(jìn)行老生代內(nèi)存中的垃圾回收。

首先是標(biāo)記階段,從一組根元素開(kāi)始,遞歸遍歷這組根元素,遍歷過(guò)程中能到達(dá)的元素稱為活動(dòng)對(duì)象,沒(méi)有到達(dá)的元素就可以判斷為非活動(dòng)對(duì)象。清除階段老生代垃圾回收器會(huì)直接將非活動(dòng)對(duì)象,也就是數(shù)據(jù)清理掉。

同樣的標(biāo)記清除策略會(huì)產(chǎn)生內(nèi)存碎片,因此還需要進(jìn)行標(biāo)記整理策略進(jìn)行優(yōu)化。

5 內(nèi)存泄漏與優(yōu)化

內(nèi)存泄漏,指在JS中已經(jīng)分配內(nèi)存地址的對(duì)象由于長(zhǎng)時(shí)間未進(jìn)行內(nèi)存釋放或無(wú)法清除,造成了長(zhǎng)期占用內(nèi)存,使得內(nèi)存資源浪費(fèi),最終導(dǎo)致運(yùn)行的應(yīng)用響應(yīng)速度變慢以及最終崩潰的情況。

在代碼中創(chuàng)建對(duì)象和變量時(shí)會(huì)占據(jù)內(nèi)存,但是JS基于自己的內(nèi)存回收機(jī)制是可以確定哪些變量不再需要,并將其進(jìn)行清除。但是,當(dāng)你的代碼中存在邏輯缺陷時(shí),你以為你已經(jīng)不需要,但是程序中還存在這引用,這就導(dǎo)致程序運(yùn)行完后并沒(méi)有進(jìn)行合適的回收所占有的內(nèi)存空間。運(yùn)行時(shí)間越長(zhǎng)占用內(nèi)存越多,隨之出現(xiàn)的問(wèn)題就是:性能不佳、高延遲、頻繁崩潰。

造成內(nèi)存泄漏的常見(jiàn)原因有:

  • 過(guò)多的緩存。及時(shí)清理過(guò)多的緩存。
  • 濫用閉包。盡量避免使用大量的閉包。
  • 定時(shí)器或回調(diào)太多。與節(jié)點(diǎn)或數(shù)據(jù)相關(guān)聯(lián)的計(jì)時(shí)器不再需要時(shí),DOM節(jié)點(diǎn)對(duì)象可以清除,整個(gè)回調(diào)函數(shù)也不再需要。可是,計(jì)時(shí)器回調(diào)函數(shù)仍然沒(méi)有被回收(計(jì)時(shí)器停止才會(huì)被回收)。當(dāng)不需要setTimeout或setInterval時(shí),定時(shí)器沒(méi)有被清除,定時(shí)器的糊掉函數(shù)以及其內(nèi)部依賴的變量都不能被回收,會(huì)造成內(nèi)存泄漏。解決方法:在定時(shí)器完成工作時(shí),需要手動(dòng)清除定時(shí)器。
  • 太多無(wú)效的DOM引用。DOM刪除了,但是節(jié)點(diǎn)的引用還在,導(dǎo)致GC無(wú)法實(shí)現(xiàn)對(duì)其所占內(nèi)存的回收。解決方法:給刪除的DOM節(jié)點(diǎn)引用設(shè)置為null。
  • 濫用全局變量。全局變量是根據(jù)定義無(wú)法被垃圾回收機(jī)制進(jìn)行收集的,因此需要特別注意臨時(shí)存儲(chǔ)和處理大量信息的全局變量。如果必須使用全局變量來(lái)存儲(chǔ)數(shù)據(jù),請(qǐng)確保將其指定為null或在完成后重新分配它。解決方法:使用嚴(yán)格模式。
  • 從外到內(nèi)執(zhí)行appendChild。此時(shí)即使調(diào)用removeChild也無(wú)法進(jìn)行釋放內(nèi)存。解決方法:從內(nèi)到外appendChild。
  • 反復(fù)重寫(xiě)同一個(gè)數(shù)據(jù)會(huì)造成內(nèi)存大量占用,但是IE瀏覽器關(guān)閉后會(huì)被釋放。
  • 注意程序邏輯,避免編寫(xiě)『死循環(huán)』之類(lèi)的代碼。
  • DOM對(duì)象和JS對(duì)象相互引用。

關(guān)于內(nèi)存泄漏,如果你想要更好地排查以及提前避免問(wèn)題的發(fā)生,最好的解決方法是通過(guò)熟練使用Chrome的內(nèi)存剖析工具,多分析多定位Chrome幫你分析保留的內(nèi)存快照,來(lái)查看持續(xù)占用大量?jī)?nèi)存的對(duì)象。

6 參考文章

  • 《「硬核JS」你真的了解垃圾回收機(jī)制嗎》
  • 《Javascript核心原理精講》
  • 《Javascript高級(jí)程序設(shè)計(jì)》

7 寫(xiě)在后面

本篇文章聊了JS的內(nèi)存管理機(jī)制,以及v8垃圾回收機(jī)制,最后我們也分析了一些日常編碼中經(jīng)常遇到內(nèi)存泄漏問(wèn)題,根據(jù)不同的原因給出對(duì)應(yīng)的解決方案。

【編輯推薦】

 

責(zé)任編輯:姜華 來(lái)源: 前端萬(wàn)有引力
相關(guān)推薦

2009-06-23 14:15:00

Java垃圾回收

2010-09-25 15:33:19

JVM垃圾回收

2017-03-03 09:26:48

PHP垃圾回收機(jī)制

2017-08-17 15:40:08

大數(shù)據(jù)Python垃圾回收機(jī)制

2011-07-04 16:48:56

JAVA垃圾回收機(jī)制GC

2017-06-12 17:38:32

Python垃圾回收引用

2021-11-05 15:23:20

JVM回收算法

2010-10-13 10:24:38

垃圾回收機(jī)制JVMJava

2021-05-27 21:47:12

Python垃圾回收

2010-09-16 15:10:24

JVM垃圾回收機(jī)制

2010-09-25 15:26:12

JVM垃圾回收

2011-06-28 12:39:34

Java垃圾回收

2015-06-04 09:38:39

Java垃圾回收機(jī)

2017-10-12 12:41:11

PHP圾回收機(jī)制變量容器

2009-12-09 17:28:34

PHP垃圾回收機(jī)制

2011-07-04 13:12:04

JavaScript

2011-01-18 14:06:58

JavaScriptweb

2023-03-26 22:48:46

Python引用計(jì)數(shù)內(nèi)存

2024-10-28 13:18:54

2011-06-28 10:19:40

C#開(kāi)發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)