W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
對于開發(fā)者來說,JavaScript 的內(nèi)存管理是自動的、無形的。我們創(chuàng)建的原始值、對象、函數(shù)……這一切都會占用內(nèi)存。
當(dāng)我們不再需要某個東西時會發(fā)生什么?JavaScript 引擎如何發(fā)現(xiàn)它并清理它?
JavaScript 中主要的內(nèi)存管理概念是 可達(dá)性。
簡而言之,“可達(dá)”值是那些以某種方式可訪問或可用的值。它們一定是存儲在內(nèi)存中的。
比方說:
這些值被稱作 根(roots)。
比方說,如果全局變量中有一個對象,并且該對象有一個屬性引用了另一個對象,則 該 對象被認(rèn)為是可達(dá)的。而且它引用的內(nèi)容也是可達(dá)的。下面是詳細(xì)的例子。
在 JavaScript 引擎中有一個被稱作 垃圾回收器 的東西在后臺執(zhí)行。它監(jiān)控著所有對象的狀態(tài),并刪除掉那些已經(jīng)不可達(dá)的。
這里是一個最簡單的例子:
// user 具有對這個對象的引用
let user = {
name: "John"
};
這里的箭頭描述了一個對象引用。全局變量 "user"
引用了對象 {name:"John"}
(為簡潔起見,我們稱它為 John)。John 的 "name"
屬性存儲一個原始值,所以它被寫在對象內(nèi)部。
如果 user
的值被重寫了,這個引用就沒了:
user = null;
現(xiàn)在 John 變成不可達(dá)的了。因為沒有引用了,就不能訪問到它了。垃圾回收器會認(rèn)為它是垃圾數(shù)據(jù)并進行回收,然后釋放內(nèi)存。
現(xiàn)在讓我們想象下,我們把 user
的引用復(fù)制給 admin
:
// user 具有對這個對象的引用
let user = {
name: "John"
};
let admin = user;
現(xiàn)在如果執(zhí)行剛剛的那個操作:
user = null;
……然后對象仍然可以被通過 admin
這個全局變量訪問到,因此它必須被保留在內(nèi)存中。如果我們又重寫了 admin
,對象就會被刪除。
現(xiàn)在來看一個更復(fù)雜的例子。這是個家庭:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
marry
函數(shù)通過讓兩個對象相互引用使它們“結(jié)婚”了,并返回了一個包含這兩個對象的新對象。
由此產(chǎn)生的內(nèi)存結(jié)構(gòu):
到目前為止,所有對象都是可達(dá)的。
現(xiàn)在讓我們移除兩個引用:
delete family.father;
delete family.mother.husband;
僅刪除這兩個引用中的一個是不夠的,因為所有的對象仍然都是可達(dá)的。
但是,如果我們把這兩個都刪除,那么我們可以看到再也沒有對 John 的引用了:
對外引用不重要,只有傳入引用才可以使對象可達(dá)。所以,John 現(xiàn)在是不可達(dá)的,并且將被從內(nèi)存中刪除,同時 John 的所有數(shù)據(jù)也將變得不可達(dá)。
經(jīng)過垃圾回收:
幾個對象相互引用,但外部沒有對其任意對象的引用,這些對象也可能是不可達(dá)的,并被從內(nèi)存中刪除。
源對象與上面相同。然后:
family = null;
內(nèi)存內(nèi)部狀態(tài)將變成:
這個例子展示了可達(dá)性概念的重要性。
顯而易見,John 和 Ann 仍然連著,都有傳入的引用。但是,這樣還不夠。
前面說的 "family"
對象已經(jīng)不再與根相連,沒有了外部對其的引用,所以它變成了一座“孤島”,并且將被從內(nèi)存中刪除。
垃圾回收的基本算法被稱為 “mark-and-sweep”。
定期執(zhí)行以下“垃圾回收”步驟:
例如,使我們的對象有如下的結(jié)構(gòu):
我們可以清楚地看到右側(cè)有一個“無法到達(dá)的島嶼”?,F(xiàn)在我們來看看“標(biāo)記和清除”垃圾收集器如何處理它。
第一步標(biāo)記所有的根:
然后,我們跟隨它們的引用標(biāo)記它們所引用的對象:
……如果還有引用的話,繼續(xù)標(biāo)記:
現(xiàn)在,無法通過這個過程訪問到的對象被認(rèn)為是不可達(dá)的,并且會被刪除。
我們還可以將這個過程想象成從根溢出一大桶油漆,它流經(jīng)所有引用并標(biāo)記所有可到達(dá)的對象。然后移除未標(biāo)記的。
這是垃圾收集工作的概念。JavaScript 引擎做了許多優(yōu)化,使垃圾回收運行速度更快,并且不會對代碼執(zhí)行引入任何延遲。
一些優(yōu)化建議:
還有其他垃圾回收算法的優(yōu)化和風(fēng)格。盡管我想在這里描述它們,但我必須打住了,因為不同的引擎會有不同的調(diào)整和技巧。而且,更重要的是,隨著引擎的發(fā)展,情況會發(fā)生變化,所以在沒有真實需求的時候,“提前”學(xué)習(xí)這些內(nèi)容是不值得的。當(dāng)然,除非你純粹是出于興趣。我在下面給你提供了一些相關(guān)鏈接。
主要需要掌握的內(nèi)容:
現(xiàn)代引擎實現(xiàn)了垃圾回收的高級算法。
《The Garbage Collection Handbook: The Art of Automatic Memory Management》(R. Jones 等人著)這本書涵蓋了其中一些內(nèi)容。
如果你熟悉底層(low-level)編程,關(guān)于 V8 引擎垃圾回收器的更詳細(xì)信息請參閱文章 V8 之旅:垃圾回收。
V8 博客 還不時發(fā)布關(guān)于內(nèi)存管理變化的文章。當(dāng)然,為了學(xué)習(xí)更多垃圾收集的相關(guān)內(nèi)容,你最好通過學(xué)習(xí) V8 引擎內(nèi)部知識來進行準(zhǔn)備,并閱讀一個名為 Vyacheslav Egorov 的 V8 引擎工程師的博客。我之所以說 “V8”,因為網(wǎng)上關(guān)于它的文章最豐富的。對于其他引擎,許多方法是相似的,但在垃圾收集上許多方面有所不同。
當(dāng)你需要底層的優(yōu)化時,對引擎有深入了解將很有幫助。在熟悉了這門編程語言之后,把熟悉引擎作為下一步計劃是明智之選。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: