溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript垃圾回收機(jī)制知識實例分析

發(fā)布時間:2022-07-29 09:26:08 來源:億速云 閱讀:158 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“JavaScript垃圾回收機(jī)制知識實例分析”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“JavaScript垃圾回收機(jī)制知識實例分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

    一、前言

    垃圾回收是JavaScript的隱藏機(jī)制,我們通常無需為垃圾回收勞心費(fèi)力,只需要專注功能的開發(fā)就好了。但是這并不意味著我們在編寫JavaScript的時候就可以高枕無憂了,伴隨著我們實現(xiàn)的功能越來越復(fù)雜,代碼量越積越大,性能問題就變的越來越突出。如何寫出執(zhí)行速度更快,而且占用內(nèi)存更小的代碼是程序員永無止歇的追求。一個優(yōu)秀的程序員總是能在極其有限的資源下,實現(xiàn)驚人的效果,這也正式蕓蕓眾生和高高在上的神祗之間的區(qū)別。

    二、何為垃圾

    代碼執(zhí)行在計算機(jī)的內(nèi)存中,我們在代碼中定義的所有變量、對象、函數(shù)都會在內(nèi)存中占用一定的內(nèi)存空間。在計算機(jī)中,內(nèi)存空間是非常緊張的資源,我們必須時時刻刻注意內(nèi)存的占用量,畢竟內(nèi)存條非常貴!如果一個變量、函數(shù)或者對象在創(chuàng)建之后不再被后繼的代碼執(zhí)行所需要,那么它就可以被稱作垃圾。

    雖然從直觀上理解垃圾的定義非常容易,但是對于一個計算機(jī)程序來說,我們很難在某一時刻斷定當(dāng)前存在的變量、函數(shù)或者對象在未來不再使用。為了降低計算機(jī)內(nèi)存的開銷,同時又保證計算機(jī)程序正常執(zhí)行,我們通常規(guī)定滿足以下任一條件的對象或者變量為垃圾:

    • 沒有被引用的對象或者變量;

    • 無法訪問到的對象(多個對象之間循環(huán)引用);

    沒有被引用的變量或者對象相當(dāng)于一座沒有門的房子,我們永遠(yuǎn)都無法進(jìn)入其中,因此不可能在用到它們。無法訪問到的對象之間雖然具備連通性,但是仍然無法從外部進(jìn)入其中,因此也無法再次被利用。滿足以上條件的對象或者變量,在程序未來執(zhí)行過程中絕對不會再次被采用,因此可以放心的當(dāng)作垃圾回收。

    當(dāng)我們通過以上定義明確了需要丟棄的對象,是否就意味著剩余的變量、對象中就沒有垃圾了呢?

    不是的!我們當(dāng)前分辨出的垃圾只是所有垃圾的一部分,仍然會有其他垃圾不滿足以上條件,但是也不會再次被使用了。

    這是否可以說滿足以上定義的垃圾是“絕對垃圾”,其他隱藏在程序中的為“相對垃圾”呢?

    三、垃圾回收

    垃圾回收機(jī)制(GC,Garbage Collection)負(fù)責(zé)在程序執(zhí)行過程中回收無用的變量和內(nèi)存占用的空間。一個對象雖然沒有再次使用的可能,但是仍然存在于內(nèi)存中的現(xiàn)象被稱為內(nèi)存泄漏。內(nèi)存泄漏是非常危險的現(xiàn)象,尤其在長時間運(yùn)行的程序中。如果一個程序出現(xiàn)了內(nèi)存泄漏,它占用的內(nèi)存空間就會越來越多,直至耗盡內(nèi)存。

    字符串、對象和數(shù)組沒有固定的大小,所以只有當(dāng)它們大小已知時才能對它們進(jìn)行動態(tài)的存儲分配。JavaScript程序每次創(chuàng)建字符串、數(shù)組或?qū)ο髸r,解釋器都要分配內(nèi)存才存儲這個實體。只要像這樣動態(tài)地分配了內(nèi)存,最終都要釋放這些內(nèi)存以便它們能夠被再次利用;否則,JavaScript的解釋器將會消耗完系統(tǒng)中所有可用的內(nèi)存,造成系統(tǒng)崩潰。

    JavaScript的垃圾回收機(jī)制會間歇性的檢查沒有用途的變量、對象(垃圾),并釋放條它們占用的空間。

    四、可達(dá)性(Reachability)

    不同的編程語言采用不同的垃圾回收策略,例如C++就沒有垃圾回收機(jī)制,所有的內(nèi)存管理靠程序員本身的技能,這也就造成了C++比較難以掌握的現(xiàn)狀。JavaScript采用可達(dá)性管理內(nèi)存,從字面意思上看,可達(dá)的意思是可以到達(dá),也就是指程序可以通過某種方式訪問、使用的變量和對象,這些變量所占用的內(nèi)存是不可以被釋放的。

    JavaScript規(guī)定了一個固有的可達(dá)值集合,集合中的值天生就是可達(dá)的:

    當(dāng)前正在執(zhí)行的函數(shù)上下文(包括函數(shù)內(nèi)的局部變量、函數(shù)的參數(shù)等);當(dāng)前嵌套調(diào)用鏈上的其他函數(shù)、它們的局部變量和參數(shù);全局變量;其他內(nèi)部的變量;

    以上變量稱為,是可達(dá)性樹的頂層節(jié)點(diǎn)。

    如果一個變量或則對象,直接或者間接的被根變量應(yīng)用,則認(rèn)為這個變量是可達(dá)的。

    換一個說法,如果一個值能夠通過根訪問到(例如,A.b.c.d.e),那么這個值就是可達(dá)的。

    五、可達(dá)性舉例

    層次關(guān)聯(lián)

    let people = {
        boys:{
            boys1:{name:'xiaoming'},
            boys2:{name:'xiaojun'},
        },
        girls:{
            girls1:{name:'xiaohong'},
            girls2:{name:'huahua'},
        }
    };

    以上代碼創(chuàng)建了一個對象,并賦值給了變量people,變量people中包含了兩個對象boysgirls,boysgirls中又分別包含了兩個子對象。這也就創(chuàng)建了一個包含了3層引用關(guān)系的數(shù)據(jù)結(jié)構(gòu)(不考慮基礎(chǔ)類型數(shù)據(jù)的情況下),

    如下圖:

    JavaScript垃圾回收機(jī)制知識實例分析

    其中,people節(jié)點(diǎn)由于是全局變量,所以天然可達(dá)。boysgirls節(jié)點(diǎn)由于被全局變量直接引用,構(gòu)成間接可達(dá)。boys1、boys2、girls1girls2由于被全局變量間接應(yīng)用,可以通過people.boys.boys訪問,因此也屬于可達(dá)變量。

    如果我們在以上代碼的后面加上以下代碼:

    people.girls.girls2 = null;
    people.girls.girls1 = people.boys.boys2;

    那么,以上引用層次圖將會變成如下形式:

    JavaScript垃圾回收機(jī)制知識實例分析

    其中,girls1girls2由于和grils節(jié)點(diǎn)斷開連接,從而變成了不可達(dá)節(jié)點(diǎn),意味著將被垃圾回收機(jī)制回收。

    而如果此時,我們再執(zhí)行以下代碼:

    people.boys.boys2 = null;

    那么引用層次圖將變成如下形式:

    JavaScript垃圾回收機(jī)制知識實例分析

    此時,雖然boys節(jié)點(diǎn)和boys2節(jié)點(diǎn)斷開了連接,但是由于boys2節(jié)點(diǎn)和girls節(jié)點(diǎn)之間存在引用關(guān)系,所以boys2仍然屬于可達(dá)的,不會被垃圾回收機(jī)制回收。

    以上關(guān)聯(lián)關(guān)系圖證明了為何稱全局變量等值為,因為在關(guān)聯(lián)關(guān)系圖中,這一類值通常作為關(guān)系樹的根節(jié)點(diǎn)出現(xiàn)。

    相互關(guān)聯(lián)

    let people = {
        boys:{
            boys1:{name:'xiaoming'},
            boys2:{name:'xiaojun'},
        },
        girls:{
            girls1:{name:'xiaohong'},
            girls2:{name:'huahua'},
        }
    };
    people.boys.boys2.girlfriend = people.girls.girls1;	//boys2引用girls1
    people.girls.girls1.boyfriend = people.boys.boys2;	//girls1引用boys2

    以上代碼在boys2girls1之間創(chuàng)建了一個相互關(guān)聯(lián)的關(guān)系,關(guān)系結(jié)構(gòu)圖如下:

    JavaScript垃圾回收機(jī)制知識實例分析

    此時,如果我們切斷boysboys2之間的關(guān)聯(lián):

    delete people.boys.boys2;

    對象之間的關(guān)聯(lián)關(guān)系圖如下:

    JavaScript垃圾回收機(jī)制知識實例分析

    顯然,并沒有不可達(dá)的節(jié)點(diǎn)出現(xiàn)。

    此時,如果我們切斷boyfriend關(guān)系連接:

    delete people.girls.girls1;

    關(guān)系圖變?yōu)椋?/strong>

    JavaScript垃圾回收機(jī)制知識實例分析

    此時,雖然boys2girls1之間還存在girlfriend關(guān)系,但是,boys2以及變?yōu)椴豢蛇_(dá)節(jié)點(diǎn),將被垃圾回收機(jī)制收回。

    可達(dá)孤島

    let people = {
        boys:{
            boys1:{name:'xiaoming'},
            boys2:{name:'xiaojun'},
        },
        girls:{
            girls1:{name:'xiaohong'},
            girls2:{name:'huahua'},
        }
    };
    delete people.boys;
    delete people.girls;

    以上代碼形成的引用層次圖如下:

    JavaScript垃圾回收機(jī)制知識實例分析

    此時,雖然虛線框內(nèi)部的對象之間仍然存在相互引用的關(guān)系,但是這些對象同樣是不可達(dá)的,并會被垃圾回收機(jī)制刪除。這些節(jié)點(diǎn)已經(jīng)和脫離了關(guān)系,變的不可達(dá)。

    六、垃圾回收算法

    引用計數(shù)

    所謂引用計-數(shù),顧名思義,就是每次對象被引用時都進(jìn)行計數(shù),增加引用就加一,刪除引用就減一,如果引用數(shù)變?yōu)?,那么就被認(rèn)定為垃圾,從而刪除對象回收內(nèi)存。

    舉個例子:

    let user = {username:'xiaoming'};//對象被user變量引用,計數(shù)+1
    let user2 = user;//對象被新的變量引用,計數(shù)+1
    user = null;//變量不再引用對象,計數(shù)-1
    user2 = null;//變量不再引用對象,奇數(shù)-1
    //此時,對象引用數(shù)為0,會被刪除

    雖然看起來引用計數(shù)方法非常合理,實際上,采用引用計數(shù)方法的內(nèi)存回收機(jī)制存在明顯的漏洞。

    例如:

    let boy = {};
    let girl = {};
    boy.girlfriend = girl;
    girl.boyfriend = boy;
    boy = null;
    girl = null;

    以上代碼在boygirl之間存在相互引用,計數(shù)刪掉boygirl內(nèi)的引用,二者對象并不會被回收。由于循環(huán)引用的存在,兩個匿名對象的引用計數(shù)永遠(yuǎn)不會歸零,也就產(chǎn)生了內(nèi)存泄漏。

    C++中存在一個智能指針shared_ptr)的概念,程序員可以通過智能指針,利用對象析構(gòu)函數(shù)釋放引用計數(shù)。但是對于循環(huán)引用的狀況就會產(chǎn)生內(nèi)存泄漏。

    好在JavaScript已經(jīng)采用了另外一種更為安全的策略,更大程度上避免了內(nèi)存泄漏的風(fēng)險。

    標(biāo)記清除

    標(biāo)記清除mark and sweep)是JavaScript引擎采取的垃圾回收算法,其基本原理是從出發(fā),廣度優(yōu)先遍歷變量之間的引用關(guān)系,對于遍歷過的變量打上一個標(biāo)記(優(yōu)秀員工徽章),最后刪除沒有標(biāo)記的對象。

    算法基本過程如下:

    • 垃圾收集器找到所有的,并頒發(fā)優(yōu)秀員工徽章(標(biāo)記);

    • 然后它遍歷優(yōu)秀員工,并將優(yōu)秀員工引用的對象同樣打上優(yōu)秀員工標(biāo)記;

    • 反復(fù)執(zhí)行第2步,直至無新的優(yōu)秀員工加入;

    • 沒有被標(biāo)記的對象都會被刪除。

    舉個栗子:

    如果我們程序中存在如下圖所示的對象引用關(guān)系:

    JavaScript垃圾回收機(jī)制知識實例分析

    我們可以清晰的看到,在整個圖片的右側(cè)存在一個“可達(dá)孤島”,從出發(fā),永遠(yuǎn)無法到達(dá)孤島。但是垃圾回收器并沒有我們這種上帝視角,它們只會根據(jù)算法會首先把根節(jié)點(diǎn)打上優(yōu)秀員工標(biāo)記。

    JavaScript垃圾回收機(jī)制知識實例分析

    然后從優(yōu)秀員工出發(fā),找到所有被優(yōu)秀員工引用的節(jié)點(diǎn),如上圖中虛線框中的三個節(jié)點(diǎn)。然后把新找到的節(jié)點(diǎn)同樣打上優(yōu)秀員工標(biāo)記。

    JavaScript垃圾回收機(jī)制知識實例分析

    反復(fù)執(zhí)行查找和標(biāo)記的過程,直至所有能找到的節(jié)點(diǎn)都被成功標(biāo)記。

    JavaScript垃圾回收機(jī)制知識實例分析

    最終達(dá)到下圖所示的效果:

    JavaScript垃圾回收機(jī)制知識實例分析

    由于在算法執(zhí)行周期結(jié)束之后,右側(cè)的孤島仍然沒有標(biāo)記,因此會被垃圾回收器任務(wù)無法到達(dá)這些節(jié)點(diǎn),最終被清除。

    如果學(xué)過數(shù)據(jù)結(jié)構(gòu)和算法的童鞋可能會驚奇的發(fā)現(xiàn),這不就是圖的遍歷嗎,類似于連通圖算法。

    七、性能優(yōu)化

    垃圾回收是一個規(guī)模龐大的工作,尤其在代碼量非常大的時候,頻繁執(zhí)行垃圾回收算法會明顯拖累程序的執(zhí)行。JavaScript算法在垃圾回收上做了很多優(yōu)化,從而在保證回收工作正常執(zhí)行的前提下,保證程序能夠高效的執(zhí)行。

    性能優(yōu)化采取的策略通常包括以下幾點(diǎn):

    分代回收

    JavaScript程序在執(zhí)行過程中會維持相當(dāng)量級的變量數(shù)目,頻繁掃描這些變量會造成明顯的開銷。但是這些變量在生命周期上各有特點(diǎn),例如局部變量會頻繁的創(chuàng)建,迅速的使用,然后丟棄,而全局變量則會長久的占據(jù)內(nèi)存。JavaScript把兩類對象分開管理,對于快速創(chuàng)建、使用并丟棄的局部變量,垃圾回收器會頻繁的掃描,保證這些變量在失去作用后迅速被清理。而對于哪些長久把持內(nèi)存的變量,降低檢查它們的頻率,從而節(jié)約一定的開銷。

    增量收集

    增量式的思想在性能優(yōu)化上非常常見,同樣可以用于垃圾回收。在變量數(shù)目非常大時,一次性遍歷所有變量并頒發(fā)優(yōu)秀員工標(biāo)記顯然非常耗時,導(dǎo)致程序在執(zhí)行過程中存在卡頓。所以,引擎會把垃圾回收工作分成多個子任務(wù),并在程序執(zhí)行的過程中逐步執(zhí)行每個小任務(wù),這樣就會造成一定的回收延遲,但通常不會造成明顯的程序卡頓。

    空閑收集

    CPU即使是在復(fù)雜的程序中也不是一直都有工作的,這主要是因為CPU工作的速度非???,外圍IO往往慢上幾個數(shù)量級,所以在CPU空閑的時候安排垃圾回收策略是一種非常有效的性能優(yōu)化手段,而且基本不會對程序本身造成不良影響。這種策略就類似于系統(tǒng)的空閑時間升級一樣,用戶根本察覺不到后臺的執(zhí)行。

    讀到這里,這篇“JavaScript垃圾回收機(jī)制知識實例分析”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI