最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解 JS 垃圾回收

 更新時(shí)間:2019年06月03日 11:37:36   作者:南波  
JS的垃圾回收機(jī)制是為了以防內(nèi)存泄漏,內(nèi)存泄漏的含義就是當(dāng)已經(jīng)不需要某塊內(nèi)存時(shí)這塊內(nèi)存還存在著,垃圾回收機(jī)制就是間歇的不定期的尋找到不再使用的變量,并釋放掉它們所指向的內(nèi)存。下面我們來一起深入學(xué)習(xí)一下吧

前言

JS之memoization,memoization 的原理是以參數(shù)作為 key,函數(shù)結(jié)果作為 value, 用對(duì)象進(jìn)行緩存起來,以內(nèi)存空間換 CPU 執(zhí)行事件。memoization 的潛在陷阱即是嚴(yán)格意義的緩存有著完善的過期策略,而普通對(duì)象的鍵值對(duì)并沒有。

用閉包進(jìn)行緩存的對(duì)象的內(nèi)存空間,不會(huì)在函數(shù)執(zhí)行完后被清除,在執(zhí)行量大和參數(shù)多樣性的情況下,會(huì)造成內(nèi)存占用且得不到釋放。

于是,本篇文章就來講講 JS 的垃圾回收。

JS 的垃圾回收機(jī)制的基本原理是:

找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存,垃圾收集器會(huì)按照固定的時(shí)間間隔周期性地執(zhí)行這一操作。

那我們?cè)趺粗雷兞渴遣皇窃诶^續(xù)使用呢?

首先,局部變量的生存周期是在函數(shù)聲明和執(zhí)行階段,函數(shù)執(zhí)行完畢后,局部變量就沒有存在的必要了。全局變量會(huì)在瀏覽器關(guān)閉或進(jìn)程關(guān)閉才能釋放。

但還有一些場(chǎng)景,比如閉包,通過作用域鏈訪問到函數(shù)外部的自由變量,使得自由變量保存在內(nèi)存中,不會(huì)隨著函數(shù)執(zhí)行完畢而結(jié)束,以及對(duì)象的相互引用等,垃圾收集器就沒這么容易判斷哪個(gè)變量有用,哪個(gè)變量沒用了。

// 經(jīng)典閉包
function closure() {
var name = "innerName";
return function() {
console.log(name);
}
}
var inner = closure();
inner(); // innerName;

所以,對(duì)于標(biāo)識(shí)無用的變量的策略可能會(huì)實(shí)現(xiàn)不同,但目前在瀏覽器中,通常有兩種策略:標(biāo)記清除和引用計(jì)數(shù)。

二、標(biāo)記-清除(Mark-Sweep)

從2012年起,所有現(xiàn)代瀏覽器都使用了標(biāo)記-清除垃圾回收算法, 那什么叫標(biāo)記-清除呢?

當(dāng)變量進(jìn)入執(zhí)行環(huán)境時(shí),就標(biāo)記這個(gè)變量為“進(jìn)入環(huán)境”。當(dāng)變量離開環(huán)境時(shí),則將其標(biāo)記為“離開環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到他們。

垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。

然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。

最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間。

另外,標(biāo)記-清除有一個(gè)問題,就是在清除之后,內(nèi)存空間是不連續(xù)的,即出現(xiàn)了內(nèi)存碎片。如果后面需要一個(gè)比較大的連續(xù)的內(nèi)存空間時(shí),那將不能滿足要求。而標(biāo)記-整理(Mark-Compact)方法可以有效地解決這個(gè)問題。標(biāo)記階段沒有什么不同,只是標(biāo)記結(jié)束后,標(biāo)記-整理方法會(huì)將活著的對(duì)象向內(nèi)存的一端移動(dòng),最后清理掉邊界的內(nèi)存。

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

另外一種不太常見的垃圾收集策略叫引用計(jì)數(shù)(Reference Counting),此算法把“對(duì)象是否不再需要”簡(jiǎn)化定義為“對(duì)象有沒有其他對(duì)象引用到它”。如果沒有引用指向該對(duì)象(零引用),對(duì)象將被垃圾回收機(jī)制回收。
引用計(jì)數(shù)的策略是跟蹤記錄每個(gè)值被使用的次數(shù),當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型賦值給該變量的時(shí)候這個(gè)值的引用次數(shù)就加 1,如果該變量的值變成了另外一個(gè),則這個(gè)值得引用次數(shù)減 1,當(dāng)這個(gè)值的引用次數(shù)變?yōu)?0 的時(shí)候,說明沒有變量在使用,這個(gè)值沒法被訪問了,因此可以將其占用的空間回收,這樣垃圾回收器會(huì)在運(yùn)行的時(shí)候清理掉引用次數(shù)為 0 的值占用的內(nèi)存。
而引用計(jì)數(shù)的不繼續(xù)被使用,是因?yàn)檠h(huán)引用的問題會(huì)引發(fā)內(nèi)存泄漏。

function problem() {
var objA = new Object();
var objB = new Object();
objA.someObject = objB;
objB.anotherObject = objA;
}

objA 和 objB 通過各自的屬性相互引用,也就是說,兩個(gè)對(duì)象的引用次數(shù)都是 2。在函數(shù)執(zhí)行完畢后,objA, objB 還將繼續(xù)存在,因?yàn)樗麄兊囊糜?jì)數(shù)永遠(yuǎn)不會(huì)是 0。假如這個(gè)函數(shù)被多次執(zhí)行,就會(huì)導(dǎo)致大量的內(nèi)存得不到釋放。

四、NodeJs V8 中的垃圾回收機(jī)制

在 Node 中,通過 JS 使用內(nèi)存時(shí)就會(huì)發(fā)現(xiàn)只能使用部分內(nèi)存(64 位系統(tǒng)下約為 1.4 GB, 32 位系統(tǒng)下約為 0.7 GB),這導(dǎo)致 Node 無法直接操作大內(nèi)存對(duì)象。

這是因?yàn)?,?1.5GB 的垃圾回收堆內(nèi)存為例,V8 做一次小的垃圾回收需要 50 毫秒以上,做一次非增量式的垃圾回收要 1 秒以上,而垃圾回收過程會(huì)引起 JS 線程暫停執(zhí)行這么多時(shí)間。因此,在當(dāng)時(shí)的考慮下,直接限制堆內(nèi)存是一個(gè)好的選擇。

那么,在這樣的內(nèi)存限制下,V8 的垃圾回收機(jī)制又有什么特點(diǎn)?

4.1、內(nèi)存分代算法

V8 的垃圾回收策略主要基于分代式垃圾回收機(jī)制,在 V8 中,將內(nèi)存分為新生代和老生代,新生代的對(duì)象為存活時(shí)間較短的對(duì)象,老生代的對(duì)象為存活事件較長(zhǎng)或常駐內(nèi)存的對(duì)象。

V8 堆的整體大小等于新生代所用內(nèi)存空間加上老生代的內(nèi)存空間,而只能在啟動(dòng)時(shí)指定,意味著運(yùn)行時(shí)無法自動(dòng)擴(kuò)充,如果超過了極限值,就會(huì)引起進(jìn)程出錯(cuò)。

4.2 Scavenge 算法

在分代的基礎(chǔ)上,新生代的對(duì)象主要通過 Scavenge 算法進(jìn)行垃圾回收,在 Scavenge 具體實(shí)現(xiàn)中,主要采用了一種復(fù)制的方式的方法—— Cheney 算法。

Cheney 算法將堆內(nèi)存一分為二,一個(gè)處于使用狀態(tài)的空間叫 From 空間,一個(gè)處于閑置狀態(tài)的空間稱為 To 空間。分配對(duì)象時(shí),先是在 From 空間中進(jìn)行分配。

當(dāng)開始進(jìn)行垃圾回收時(shí),會(huì)檢查 From 空間中的存活對(duì)象,將其復(fù)制到 To 空間中,而非存活對(duì)象占用的空間將會(huì)被釋放。完成復(fù)制后,F(xiàn)rom 空間和 To 空間的角色發(fā)生對(duì)換。

當(dāng)一個(gè)對(duì)象經(jīng)過多次復(fù)制后依然存活,他將會(huì)被認(rèn)為是生命周期較長(zhǎng)的對(duì)象,隨后會(huì)被移動(dòng)到老生代中,采用新的算法進(jìn)行管理。

還有一種情況是,如果復(fù)制一個(gè)對(duì)象到 To 空間時(shí),To 空間占用超過了 25%,則這個(gè)對(duì)象會(huì)被直接晉升到老生代空間中。

4.3 標(biāo)記-清除和標(biāo)記-整理算法

對(duì)于老生代中的對(duì)象,主要采用標(biāo)記-清除和標(biāo)記-整理算法。標(biāo)記-清除 和前文提到的標(biāo)記一樣,與 Scavenge 算法相比,標(biāo)記清除不會(huì)將內(nèi)存空間劃為兩半,標(biāo)記清除在標(biāo)記階段會(huì)標(biāo)記活著的對(duì)象,而在內(nèi)存回收階段,它會(huì)清除沒有被標(biāo)記的對(duì)象。
而標(biāo)記整理是為了解決標(biāo)記清除后留下的內(nèi)存碎片問題。

4.4 增量標(biāo)記(Incremental Marking)算法

前面的三種算法,都需要將正在執(zhí)行的 JavaScript 應(yīng)用邏輯暫停下來,待垃圾回收完畢后再恢復(fù)。這種行為叫作“全停頓”(stop-the-world)。

在 V8 新生代的分代回收中,只收集新生代,而新生代通常配置較小,且存活對(duì)象較少,所以全停頓的影響不大,而老生代就相反了。

為了降低全部老生代全堆垃圾回收帶來的停頓時(shí)間,V8將標(biāo)記過程分為一個(gè)個(gè)的子標(biāo)記過程,同時(shí)讓垃圾回收標(biāo)記和JS應(yīng)用邏輯交替進(jìn)行,直到標(biāo)記階段完成。

經(jīng)過增量標(biāo)記改進(jìn)后,垃圾回收的最大停頓時(shí)間可以減少到原來的 1/6 左右。

五、內(nèi)存泄漏

內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。

六、內(nèi)存泄漏的常見場(chǎng)景

6.1 緩存

文章前言部分就有說到,JS 開發(fā)者喜歡用對(duì)象的鍵值對(duì)來緩存函數(shù)的計(jì)算結(jié)果,但是緩存中存儲(chǔ)的鍵越多,長(zhǎng)期存活的對(duì)象也就越多,這將導(dǎo)致垃圾回收在進(jìn)行掃描和整理時(shí),對(duì)這些對(duì)象做無用功。

6.2 作用域未釋放(閉包)

var leakArray = [];
exports.leak = function () {
leakArray.push("leak" + Math.random());
}

以上代碼,模塊在編譯執(zhí)行后形成的作用域因?yàn)槟K緩存的原因,不被釋放,每次調(diào)用 leak 方法,都會(huì)導(dǎo)致局部變量 leakArray 不停增加且不被釋放。

閉包可以維持函數(shù)內(nèi)部變量駐留內(nèi)存,使其得不到釋放。

6.3 沒必要的全局變量

聲明過多的全局變量,會(huì)導(dǎo)致變量常駐內(nèi)存,要直到進(jìn)程結(jié)束才能夠釋放內(nèi)存。

6.4 無效的 DOM 引用

//dom still exist
function click(){
// 但是 button 變量的引用仍然在內(nèi)存當(dāng)中。
const button = document.getElementById('button');
button.click();
}
// 移除 button 元素
function removeBtn(){
document.body.removeChild(document.getElementById('button'));
}

6.5 定時(shí)器未清除

// vue 的 mounted 或 react 的 componentDidMount
componentDidMount() {
setInterval(function () {
// ...do something
}, 1000)
}

vue 或 react 的頁面生命周期初始化時(shí),定義了定時(shí)器,但是在離開頁面后,未清除定時(shí)器,就會(huì)導(dǎo)致內(nèi)存泄漏。

6.6 事件監(jiān)聽為清空

componentDidMount() {
window.addEventListener("scroll", function () {
// do something...
});
}

同 6.5, 在頁面生命周期初始化時(shí),綁定了事件監(jiān)聽器,但在離開頁面后,未清除事件監(jiān)聽器,同樣也會(huì)導(dǎo)致內(nèi)存泄漏。

七、內(nèi)存泄漏優(yōu)化

1.解除引用

確保占用最少的內(nèi)存可以讓頁面獲得更好的性能。而優(yōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)。一旦數(shù)據(jù)不再有用,最好通過將其值設(shè)置為 null 來釋放其引用——這個(gè)做法叫做解除引用(dereferencing)

function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手動(dòng)解除 globalPerson 的引用
globalPerson = null;

解除一個(gè)值的引用并不意味著自動(dòng)回收該值所占用的內(nèi)存。解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行時(shí)將其回收。

2.提供手動(dòng)清空變量的方法

var leakArray = [];
exports.clear = function () {
leakArray = [];
}

3.在業(yè)務(wù)不需要用到的內(nèi)部函數(shù),可以重構(gòu)在函數(shù)外,實(shí)現(xiàn)解除閉包
4.避免創(chuàng)建過多生命周期較長(zhǎng)的對(duì)象,或?qū)?duì)象分解成多個(gè)子對(duì)象

5.避免過多使用閉包

6.注意清除定時(shí)器和事件監(jiān)聽器

7.Nodejs 中使用 stream 或 buffer 來操作大文件,不會(huì)受 Nodejs 內(nèi)存限制

8.使用 redis 等外部工具緩存數(shù)據(jù)

總結(jié)

JS 是一門具有自動(dòng)垃圾收集的編程語言,在瀏覽器中主要通過標(biāo)記清除方法來回收垃圾,NodeJs 中主要通過分代回收、Scavenge、標(biāo)記清除、增量標(biāo)記等算法來回收垃圾。在日常開發(fā)中,有一些不引人注意的書寫方式可能會(huì)導(dǎo)致內(nèi)存泄漏,需要多注意自己的代碼規(guī)范。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • javascript類型轉(zhuǎn)換使用方法

    javascript類型轉(zhuǎn)換使用方法

    這篇文章主要介紹了javascript類型轉(zhuǎn)換的使用方法,包括轉(zhuǎn)換為字符串、轉(zhuǎn)換為數(shù)字、轉(zhuǎn)換為布爾值、轉(zhuǎn)換為對(duì)象使用方法,大家參考使用吧
    2014-02-02
  • 深入理解JavaScript中的對(duì)象

    深入理解JavaScript中的對(duì)象

    這篇文章主要介紹了深入理解JavaScript中的對(duì)象,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-06-06
  • Javascript基礎(chǔ)_嵌入圖像的簡(jiǎn)單實(shí)現(xiàn)

    Javascript基礎(chǔ)_嵌入圖像的簡(jiǎn)單實(shí)現(xiàn)

    下面小編就為大家?guī)硪黄狫avascript 基礎(chǔ)_嵌入圖像的簡(jiǎn)單實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • php的文件上傳入門教程(實(shí)例講解)

    php的文件上傳入門教程(實(shí)例講解)

    文件作為一種特殊的表單數(shù)據(jù),通過http post請(qǐng)求方式提交至服務(wù)器的時(shí)候,php會(huì)生成一個(gè)$_FILES全局?jǐn)?shù)組,相關(guān)的文件信息會(huì)存放在這個(gè)全局?jǐn)?shù)組中。我將在這篇文章中通過一些示例代碼來闡述php下的文件上傳,并且深入看下關(guān)于文件上傳內(nèi)部的實(shí)現(xiàn)機(jī)制,最后簡(jiǎn)單說下如何加強(qiáng)這方面的安全性!
    2014-04-04
  • 整理的比較不錯(cuò)的JavaScript的方法和技巧

    整理的比較不錯(cuò)的JavaScript的方法和技巧

    整理的比較不錯(cuò)的JavaScript的方法和技巧...
    2007-02-02
  • 關(guān)于JavaScript的Array數(shù)組方法詳解

    關(guān)于JavaScript的Array數(shù)組方法詳解

    這篇文章主要介紹了關(guān)于JavaScript的Array數(shù)組方法詳解,數(shù)組是一個(gè)固定長(zhǎng)度的存儲(chǔ)相同數(shù)據(jù)類型的數(shù)據(jù)結(jié)構(gòu),數(shù)組中的元素被存儲(chǔ)在一段連續(xù)的內(nèi)存空間中,它是最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)之一,需要的朋友可以參考下
    2023-05-05
  • JavaScript中Number.MAX_VALUE屬性的使用方法

    JavaScript中Number.MAX_VALUE屬性的使用方法

    這篇文章主要介紹了JavaScript中Number.MAX_VALUE屬性的使用方法,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-06-06
  • 淺談重寫window對(duì)象的方法

    淺談重寫window對(duì)象的方法

    這篇文章主要介紹了重寫window對(duì)象的方法,需要的朋友可以參考下
    2014-12-12
  • Javascript學(xué)習(xí)筆記4 Eval函數(shù)

    Javascript學(xué)習(xí)筆記4 Eval函數(shù)

    在初學(xué)JS的時(shí)候就知道這個(gè)函數(shù),卻一直沒有了解過他的用途,也一直都是睜一只眼閉一只眼,這次來深入地了解一下這個(gè)函數(shù)的作用。
    2010-01-01
  • JavaScript那些不經(jīng)意間發(fā)生的數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換

    JavaScript那些不經(jīng)意間發(fā)生的數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換

    JavaScript可以自由的進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,但是更多的情況下,是由JavaScript自動(dòng)轉(zhuǎn)換的。本文就將為大家詳細(xì)講解那些不經(jīng)意間發(fā)生的數(shù)據(jù)類型轉(zhuǎn)換,感興趣的同學(xué)可以了解一下
    2022-02-02

最新評(píng)論

河北省| 澄城县| 且末县| 涞源县| 遵义市| 塔城市| 石首市| 兴文县| 休宁县| 大荔县| 嵊泗县| 吴堡县| 蓬溪县| 宣汉县| 阿拉善左旗| 犍为县| 勃利县| 且末县| 庆云县| 东乌珠穆沁旗| 富裕县| 龙山县| 皮山县| 白城市| 许昌市| 河间市| 五大连池市| 福贡县| 栾城县| 河北省| 张家口市| 桑日县| 桐乡市| 利津县| 驻马店市| 连州市| 桂林市| 竹溪县| 永城市| 剑川县| 清新县|