JavaScript自動(dòng)內(nèi)存管理與垃圾回收策略詳細(xì)分析講解
自動(dòng)內(nèi)存管理
JavaScript編程語言通過自動(dòng)內(nèi)存管理實(shí)現(xiàn)內(nèi)存分配和閑置資源回收。
簡(jiǎn)單來講就是:只要確定某個(gè)變量X不會(huì)再被使用了,就將變量X占用的內(nèi)存進(jìn)行釋放。這種判斷是周期性執(zhí)行的,即:垃圾回收程序隔一定時(shí)間就會(huì)自動(dòng)執(zhí)行一次,以釋放某些不必要的內(nèi)存開支。
JavaScript垃圾回收過程中的難點(diǎn)在于:如何正確判定一塊內(nèi)存是否還有用?
垃圾回收策略
在C/C++程序中,我們記憶比較深刻的可能是它的“指針機(jī)制”,因?yàn)檫@些指針的存在,內(nèi)存管理是靠程序員手動(dòng)分配和釋放的。相較JavaScript的自動(dòng)內(nèi)存管理方式,顯得更為繁瑣和復(fù)雜。
但是,如第一部分所講,JS垃圾回收的難點(diǎn)在于:如何判定一塊內(nèi)存是否還有用?舉個(gè)簡(jiǎn)單的例子,以函數(shù)call()的調(diào)用為例:
<script> function call(){ const name = 'Hello'; console.log(name); } call(); </script>
在調(diào)用函數(shù)時(shí),會(huì)為函數(shù)體的執(zhí)行開辟新的內(nèi)存空間。call()函數(shù)內(nèi)部定義了局部變量name,并且將其輸出在控制臺(tái);輸出結(jié)束,call()函數(shù)也執(zhí)行結(jié)束,函數(shù)體執(zhí)行過程退出;此時(shí),不再需要call()函數(shù)的局部變量name,那么,name占用的內(nèi)存就可以被釋放了,空出來的內(nèi)存在之后可以被程序的其它部分使用。
但是,并非所有的內(nèi)存有用性判定都會(huì)如此清晰,假如:在call()函數(shù)內(nèi)部形成了閉包,對(duì)變量的生存周期進(jìn)行了延展處理等等,所以,垃圾回收程序必須去追蹤記錄變量的狀態(tài),以確定:哪個(gè)變量還會(huì)被使用?哪個(gè)變量不會(huì)再被使用?以便于內(nèi)存回收。
而如何標(biāo)記未使用的變量,也有不同的實(shí)現(xiàn)方式。在瀏覽器的發(fā)展史上,用到過兩種如下的主要標(biāo)記策略。
標(biāo)記清理策略
JavaScript最常用的垃圾回收策略是“標(biāo)記清理(mark-and-sweep)”。
基本思路是:當(dāng)變量進(jìn)入上下文,比如:函數(shù)內(nèi)部聲明一個(gè)變量時(shí),這個(gè)變量就會(huì)被加載存在于上下文中的標(biāo)記。而不在上下文中的變量,邏輯上講,永遠(yuǎn)不應(yīng)該釋放它們的內(nèi)存,因?yàn)橹灰舷挛牡拇a在運(yùn)行,就有可能用到這些不在上下文中的變量。此外,當(dāng)變量離開上下文時(shí),也會(huì)被加上離開上下文的標(biāo)記。
在垃圾回收程序運(yùn)行的時(shí)候,會(huì)使用新的標(biāo)記符號(hào)標(biāo)記內(nèi)存中存儲(chǔ)的所有變量。然后,將所有在上下文中的變量、以及被在上下文中的變量引用的變量上面的標(biāo)記去掉,經(jīng)過一輪篩選,剩下的仍然帶有特殊標(biāo)記的變量就是將要被刪除的垃圾了。原因在于:這些帶有特殊標(biāo)記的變量在任何上下文中的都不會(huì)被使用到了。接著,垃圾回收程序執(zhí)行一次內(nèi)存清理,銷毀帶標(biāo)記的所有值并回收它們的內(nèi)存。
至2008年,IE、FireFox、Opera、Chrome和Safari都在自己的JavaScript實(shí)現(xiàn)中采用這種標(biāo)記清理策略,只是在實(shí)現(xiàn)細(xì)節(jié)上有所不同。
引用計(jì)數(shù)策略
“引用計(jì)數(shù)(reference counting)”策略不太常用,其思路是:對(duì)每個(gè)值都記錄它被引用的次數(shù)。例如:聲明一個(gè)變量X并給X賦值為999,此時(shí),這個(gè)值999的引用次數(shù)就為1.如果同一個(gè)值999又被賦值給另一個(gè)變量Y,那么值999的引用次數(shù)+1=2.類似的,如果保存該值999的引用變量X被其它值(如:null)給覆蓋掉了,那么值999的引用次數(shù)就-1=1.
而當(dāng)一個(gè)值的引用數(shù)為0時(shí),就說明沒有任何變量引用到這個(gè)值了,就可以安全地收回內(nèi)存了。當(dāng)垃圾回收程序下一次執(zhí)行時(shí),就可以釋放引用數(shù)為0的值所在的內(nèi)存空間。
但是,這種策略存在“循環(huán)引用”的問題,導(dǎo)致某些值的引用數(shù)永遠(yuǎn)不會(huì)變?yōu)榱悖簿蜎]有機(jī)會(huì)被清理掉,引發(fā)內(nèi)存泄露。
內(nèi)存管理技巧
在使用垃圾回收的編程環(huán)境中,開發(fā)者通常無需關(guān)心內(nèi)存管理。但是,JavaScript運(yùn)行在一個(gè)內(nèi)存管理和垃圾回收都十分特殊的環(huán)境中。因?yàn)椴僮飨到y(tǒng)可分配給承載JavaScript程序的網(wǎng)頁程序的內(nèi)存量是十分有限的,這種內(nèi)存限制不僅影響變量分配,同時(shí)也影響調(diào)用棧以及能夠在同一個(gè)線程中執(zhí)行的語句數(shù)量。
將網(wǎng)頁程序的內(nèi)存占用量保持在一個(gè)較小的值,可以讓頁面的性能更優(yōu)。
解除引用
優(yōu)化內(nèi)存占用的最佳手段就是保證在程序運(yùn)行過程中只保存必要的數(shù)據(jù),如果數(shù)據(jù)不再必要,那么就將其設(shè)置為null,從而釋放其引用,這可稱為“解除引用”。這個(gè)技巧最適合用于全局變量和全局對(duì)象的屬性上,因?yàn)榫植孔兞吭诔鲎畲笞饔糜蛑螅蜁?huì)被自動(dòng)解除引用。
如下,函數(shù)createObject()執(zhí)行完畢,會(huì)返回一個(gè)Object對(duì)象,同時(shí)內(nèi)部的局部變量obj會(huì)自動(dòng)解除與new Object()對(duì)象之間的引用關(guān)系;而在全局范圍內(nèi),globalObject對(duì)象獲取到了Object對(duì)象的引用;隨后,當(dāng)該對(duì)象不再被使用時(shí),就應(yīng)當(dāng)由開發(fā)者主動(dòng)解除引用。
//創(chuàng)建一個(gè)對(duì)象 function createObject(name){ let obj = new Object(); obj.name = name; return obj; } //全局變量 const globalObject = createObject("Tom"); //當(dāng)該對(duì)象不再被使用時(shí),進(jìn)行解除引用 globalObject = null;
解除對(duì)一個(gè)值的引用的并不會(huì)自動(dòng)導(dǎo)致相關(guān)的內(nèi)存被回收,而是在于,解除引用可以保證相關(guān)的值不再存在于上下文環(huán)境中了,這樣,垃圾回收程序下一次執(zhí)行時(shí),這個(gè)值所在的內(nèi)存空間就會(huì)被回收。
const和let變量聲明
ES6新增的關(guān)鍵字const、let,不僅有助于改善代碼風(fēng)格,而且同樣有助于垃圾回收的過程。因?yàn)閏onst和let都以塊(而非函數(shù))為作用域,所以相比于var,使用這兩個(gè)關(guān)鍵字,可能會(huì)更早的讓垃圾回收程序介入,盡早回收掉應(yīng)該回收的內(nèi)存。
到此這篇關(guān)于JavaScript自動(dòng)內(nèi)存管理與垃圾回收策略詳細(xì)分析講解的文章就介紹到這了,更多相關(guān)JS自動(dòng)內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
完美解決手機(jī)瀏覽器頂部下拉出現(xiàn)網(wǎng)頁源或刷新的問題
下面小編就為大家分享一篇完美解決手機(jī)瀏覽器頂部下拉出現(xiàn)網(wǎng)頁源或刷新的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-11-11javascript中的深復(fù)制詳解及實(shí)例分析
這篇文章主要介紹了javascript中的深復(fù)制詳解及實(shí)例分析的相關(guān)資料,需要的朋友可以參考下2016-12-12js實(shí)現(xiàn)飛機(jī)大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)飛機(jī)大戰(zhàn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08javascript輕量級(jí)模板引擎juicer使用指南
Juicer 是一個(gè)高效、輕量的前端 (Javascript) 模板引擎,使用 Juicer 可以是你的代碼實(shí)現(xiàn)數(shù)據(jù)和視圖模型的分離(MVC)。2014-06-06如何消除inline-block屬性帶來的標(biāo)簽間間隙
這篇文章主要介紹了如何消除inline-block屬性帶來的標(biāo)簽間間隙的相關(guān)資料,需要的朋友可以參考下2016-03-03JS基于ocanvas插件實(shí)現(xiàn)的簡(jiǎn)單畫板效果代碼(附demo源碼下載)
這篇文章主要介紹了JS基于ocanvas插件實(shí)現(xiàn)的簡(jiǎn)單畫板效果,結(jié)合實(shí)例形式分析了ocanvas插件實(shí)現(xiàn)畫板的相關(guān)技巧,并附代碼demo源碼供讀者下載參考,需要的朋友可以參考下2016-04-04移動(dòng)端點(diǎn)擊圖片放大特效PhotoSwipe.js插件實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了移動(dòng)端點(diǎn)擊圖片放大特效PhotoSwipe.js插件實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08