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