中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

JavaScript隱藏機制之垃圾回收知識點有哪些

發布時間:2022-06-09 10:09:35 來源:億速云 閱讀:86 作者:zzz 欄目:web開發

這篇文章主要介紹“JavaScript隱藏機制之垃圾回收知識點有哪些”,在日常操作中,相信很多人在JavaScript隱藏機制之垃圾回收知識點有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JavaScript隱藏機制之垃圾回收知識點有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

JavaScript隱藏機制之垃圾回收知識點有哪些

一、前言

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

二、何為垃圾

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

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

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

  2. 無法訪問到的對象(多個對象之間循環引用);

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

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

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

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

三、垃圾回收

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

字符串、對象和數組沒有固定的大小,所以只有當它們大小已知時才能對它們進行動態的存儲分配。JavaScript程序每次創建字符串、數組或對象時,解釋器都要分配內存才存儲這個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便它們能夠被再次利用;否則,JavaScript的解釋器將會消耗完系統中所有可用的內存,造成系統崩潰。

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

四、可達性(Reachability)

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

JavaScript規定了一個固有的可達值集合,集合中的值天生就是可達的:

  1. 當前正在執行的函數上下文(包括函數內的局部變量、函數的參數等);

  2. 當前嵌套調用鏈上的其他函數、它們的局部變量和參數;

  3. 全局變量;

  4. 其他內部的變量;

以上變量稱為,是可達性樹的頂層節點。

如果一個變量或則對象,直接或者間接的被根變量應用,則認為這個變量是可達的。

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

五、可達性舉例

層次關聯:

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

以上代碼創建了一個對象,并賦值給了變量people,變量people中包含了兩個對象boysgirlsboysgirls中又分別包含了兩個子對象。這也就創建了一個包含了3層引用關系的數據結構(不考慮基礎類型數據的情況下),如下圖:

JavaScript隱藏機制之垃圾回收知識點有哪些

其中,people節點由于是全局變量,所以天然可達。boysgirls節點由于被全局變量直接引用,構成間接可達。boys1boys2girls1girls2由于被全局變量間接應用,可以通過people.boys.boys訪問,因此也屬于可達變量。

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

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

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

JavaScript隱藏機制之垃圾回收知識點有哪些

其中,girls1girls2由于和grils節點斷開連接,從而變成了不可達節點,意味著將被垃圾回收機制回收。

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

people.boys.boys2 = null;

那么引用層次圖將變成如下形式:
JavaScript隱藏機制之垃圾回收知識點有哪些

此時,雖然boys節點和boys2節點斷開了連接,但是由于boys2節點和girls節點之間存在引用關系,所以boys2仍然屬于可達的,不會被垃圾回收機制回收。

以上關聯關系圖證明了為何稱全局變量等值為,因為在關聯關系圖中,這一類值通常作為關系樹的根節點出現。

相互關聯:

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

以上代碼在boys2girls1之間創建了一個相互關聯的關系,關系結構圖如下:

JavaScript隱藏機制之垃圾回收知識點有哪些

此時,如果我們切斷boysboys2之間的關聯:

delete people.boys.boys2;

對象之間的關聯關系圖如下:

JavaScript隱藏機制之垃圾回收知識點有哪些

顯然,并沒有不可達的節點出現。

此時,如果我們切斷boyfriend關系連接:

delete people.girls.girls1;

關系圖變為:

JavaScript隱藏機制之垃圾回收知識點有哪些

此時,雖然boys2girls1之間還存在girlfriend關系,但是,boys2以及變為不可達節點,將被垃圾回收機制收回。

可達孤島:

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

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

JavaScript隱藏機制之垃圾回收知識點有哪些

此時,雖然虛線框內部的對象之間仍然存在相互引用的關系,但是這些對象同樣是不可達的,并會被垃圾回收機制刪除。這些節點已經和脫離了關系,變的不可達。

六、垃圾回收算法

引用計數

所謂引用計-數,顧名思義,就是每次對象被引用時都進行計數,增加引用就加一,刪除引用就減一,如果引用數變為0,那么就被認定為垃圾,從而刪除對象回收內存。

舉個例子:

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

雖然看起來引用計數方法非常合理,實際上,采用引用計數方法的內存回收機制存在明顯的漏洞。

例如:

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

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

C++中存在一個智能指針shared_ptr)的概念,程序員可以通過智能指針,利用對象析構函數釋放引用計數。但是對于循環引用的狀況就會產生內存泄漏。

好在JavaScript已經采用了另外一種更為安全的策略,更大程度上避免了內存泄漏的風險。

標記清除

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

算法基本過程如下:

  1. 垃圾收集器找到所有的,并頒發優秀員工徽章(標記);

  2. 然后它遍歷優秀員工,并將優秀員工引用的對象同樣打上優秀員工標記;

  3. 反復執行第2步,直至無新的優秀員工加入;

  4. 沒有被標記的對象都會被刪除。

舉個栗子:

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

JavaScript隱藏機制之垃圾回收知識點有哪些

我們可以清晰的看到,在整個圖片的右側存在一個“可達孤島”,從出發,永遠無法到達孤島。但是垃圾回收器并沒有我們這種上帝視角,它們只會根據算法會首先把根節點打上優秀員工標記。

JavaScript隱藏機制之垃圾回收知識點有哪些

然后從優秀員工出發,找到所有被優秀員工引用的節點,如上圖中虛線框中的三個節點。然后把新找到的節點同樣打上優秀員工標記。

JavaScript隱藏機制之垃圾回收知識點有哪些

反復執行查找和標記的過程,直至所有能找到的節點都被成功標記。

JavaScript隱藏機制之垃圾回收知識點有哪些

最終達到下圖所示的效果:

JavaScript隱藏機制之垃圾回收知識點有哪些

由于在算法執行周期結束之后,右側的孤島仍然沒有標記,因此會被垃圾回收器任務無法到達這些節點,最終被清除。

如果學過數據結構和算法的童鞋可能會驚奇的發現,這不就是圖的遍歷嗎,類似于連通圖算法。

七、性能優化

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

性能優化采取的策略通常包括以下幾點:

分代回收

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

增量收集

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

空閑收集

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

到此,關于“JavaScript隱藏機制之垃圾回收知識點有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

仲巴县| 普安县| 青海省| 互助| 玉田县| 遂昌县| 焦作市| 怀远县| 建宁县| 灵台县| 东阿县| 崇礼县| 荔波县| 新竹县| 邻水| 新营市| 长丰县| 罗江县| 满城县| 弋阳县| 双峰县| 永吉县| 扶余县| 基隆市| 银川市| 怀来县| 天水市| 车致| 新宾| 永登县| 乐业县| 山东省| 鸡泽县| 黑水县| 天长市| 卫辉市| 门源| 怀化市| 安庆市| 额尔古纳市| 开江县|