摘要:是如何工作的內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏原文譯者幾個(gè)禮拜之前我們開始一系列對(duì)于以及其本質(zhì)工作原理的深入挖掘我們認(rèn)為通過了解的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應(yīng)用。
JavaScript是如何工作的:內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏
原文:How JavaScript works: memory management + how to handle 4 common memory leaks
譯者:neal1991
welcome to star my articles-translator , providing you advanced articles translation. Any suggestion, please issue or contact me
LICENSE: MIT
幾個(gè)禮拜之前我們開始一系列對(duì)于JavaScript以及其本質(zhì)工作原理的深入挖掘:我們認(rèn)為通過了解JavaScript的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應(yīng)用。
這個(gè)系列的第一篇博客專注于介紹對(duì)于引擎,運(yùn)行時(shí)以及調(diào)用棧的概述(譯者注:第一篇博客翻譯版)。第二篇博客近距離地檢測(cè)了Google V8 引擎的內(nèi)部并且提供了一些如何寫出更好的JavaScript代碼的建議。
在第三篇博客中,我們將會(huì)討論另外一個(gè)關(guān)鍵的話題。這個(gè)話題由于隨著編程語言的逐漸成熟和復(fù)雜化,越來越被開發(fā)者所忽視,這個(gè)話題就是在日常工作中使用到的——內(nèi)存管理。我們還將提供一些有關(guān)如何處理我們?cè)赟essionStack中的JavaScript中的內(nèi)存泄漏的建議,因?yàn)槲覀冃枰_保SessionStack不會(huì)導(dǎo)致內(nèi)存泄漏或者增加我們集成的Web應(yīng)用程序的內(nèi)存消耗。
概述語言,比如C,具有低層次的內(nèi)存管理方法,比如malloc()以及free()。開發(fā)者利用這些方法精確地為操作系統(tǒng)分配以及釋放內(nèi)存。
同時(shí),JavaScript會(huì)在創(chuàng)建一些變量(對(duì)象,字符串等等)的時(shí)候分配內(nèi)存,并且會(huì)在這些不被使用之后“自動(dòng)地”釋放這些內(nèi)存,這個(gè)過程被稱為垃圾收集。這個(gè)看起來“自動(dòng)化的”特性其實(shí)就是產(chǎn)生誤解的原因,并且給JavaScript(以及其他高層次語言)開發(fā)者一個(gè)假象,他們不需要關(guān)心內(nèi)存管理。大錯(cuò)特錯(cuò)。
即使是使用高層次語言,開發(fā)者應(yīng)該對(duì)于內(nèi)存管理有一定的理解(或者最基本的理解)。有時(shí)候自動(dòng)的內(nèi)存管理會(huì)存在一些問題(比如一些bug或者垃圾收集器的一些限制等等),對(duì)于這些開發(fā)者必須能夠理解從而能夠合適地處理(或者使用最小的代價(jià)以及代碼債務(wù)去繞過這個(gè)問題)。
內(nèi)存生命周期不管你在使用什么編程語言,內(nèi)存的生命周期基本上都是一樣的:
下面是對(duì)于周期中每一步所發(fā)生的情況的概述:
分配內(nèi)存——操作系統(tǒng)為你的程序分配內(nèi)存并且允許其使用。在低層次語言中(比如C),這正是開發(fā)者應(yīng)該處理的操作。在高層次的語言,然而,就由語言幫你實(shí)現(xiàn)了。
使用內(nèi)存——當(dāng)你的程序確實(shí)在使用之前分配的內(nèi)存的階段。當(dāng)你在使用你代碼里面分配的變量的時(shí)候會(huì)發(fā)生讀以及寫操作。
釋放內(nèi)存——這個(gè)階段就是釋放你不再需要的內(nèi)存,從而這些內(nèi)存被釋放并且能夠再次被使用。和分配內(nèi)存操作一樣,這在低層次的語言也是開發(fā)者需要明確的操作。
對(duì)于調(diào)用棧以及內(nèi)存堆有一個(gè)快速的概念認(rèn)識(shí),你可以閱讀我們關(guān)于這個(gè)話題的第一篇博客。
什么是內(nèi)存?在我們講述JavaScript內(nèi)存之前,我們將簡要地討論一下內(nèi)存是什么以及它們是如何在 nutshell 中工作的。
在硬件層次上,計(jì)算機(jī)內(nèi)存由大量的 寄存器 組成。每一個(gè)寄存器都包含一些晶體管并且能夠存儲(chǔ)一比特。多帶帶的寄存器可以通過獨(dú)特的標(biāo)識(shí)符去訪問,因此我們能夠讀取以及重寫它們。因此,從概念上來說,我們可以認(rèn)為我們的整個(gè)計(jì)算機(jī)內(nèi)存就是一個(gè)我們能夠讀寫的大型比特?cái)?shù)組。
因?yàn)樽鳛槿祟悾覀儾簧瞄L直接基于比特進(jìn)行思考以及算術(shù),我們將它們組織成大規(guī)模群組,它們?cè)谝黄鹂梢源硪粋€(gè)數(shù)字。8個(gè)比特稱為一個(gè)字節(jié)。除了字節(jié),還有詞(有時(shí)候是16比特,有時(shí)候是32比特)。
內(nèi)存中存儲(chǔ)了很多東西:
所有程序使用的變量和其他數(shù)據(jù)
程序的代碼,包括操作系統(tǒng)的代碼。
編譯器和操作系統(tǒng)共同合作為你處理大部分的內(nèi)存管理,但是我們建議你應(yīng)該了解其內(nèi)部的運(yùn)行原理。
當(dāng)你編譯你的代碼的時(shí)候,編譯器將會(huì)檢查原始數(shù)據(jù)類型并且提前計(jì)算好它們需要多少內(nèi)存。需要的內(nèi)存被分配給程序,這被稱為棧空間。這些被分配給變量的空間被稱為棧空間,因?yàn)橐坏┖瘮?shù)被調(diào)用,它們的內(nèi)存就會(huì)增加到現(xiàn)有內(nèi)存的上面。當(dāng)它們終止的時(shí)候,它們就會(huì)以后進(jìn)先出(LIFO)的順序移除。比如,考慮下面的聲明。
int n; // 4 bytes int x[4]; // array of 4 elements, each 4 bytes double m; // 8 bytes
編譯器能夠立即計(jì)算出代碼需要
4 + 4 × 4 + 8 = 28 字節(jié)
那就是它如何對(duì)于現(xiàn)有的整形以及雙浮點(diǎn)型工作。大約20年前,整形典型都是2個(gè)字節(jié),雙浮點(diǎn)型是4個(gè)字節(jié)。你的代碼不應(yīng)該取決于當(dāng)下基本數(shù)據(jù)類型的大小。
編譯器將會(huì)插入能夠與操作系統(tǒng)交互的代碼,從而在棧上獲取你需要存儲(chǔ)變量需要的字節(jié)數(shù)。
在上述的例子中,編譯器知道每一個(gè)變量的準(zhǔn)確的內(nèi)存地址。事實(shí)上,無論我們何時(shí)寫變量 n ,這都會(huì)在內(nèi)部轉(zhuǎn)化為類似于“內(nèi)存地址 4127963”的東西。
注意如果我們希望在這訪問 x[4] 我們將會(huì)需要訪問和 m 相關(guān)聯(lián)的數(shù)據(jù)。這是因?yàn)槲覀冊(cè)谠L問數(shù)組里面并不存在的元素——它比數(shù)組實(shí)際分配的最后一個(gè)元素 x[3] 要多4個(gè)字節(jié),并且最后可能是閱讀(或者重寫)一些 m 的比特。這將很可能給程序的其他部分帶來一些不良的后果。
當(dāng)函數(shù)調(diào)用其它函數(shù)的時(shí)候,當(dāng)它被調(diào)用的時(shí)候都會(huì)獲取它自己的堆棧塊。它在那保存了它所有的局部變量,但是還會(huì)有一個(gè)程序計(jì)數(shù)器記錄它執(zhí)行的位置。當(dāng)這個(gè)函數(shù)執(zhí)行完畢,它的內(nèi)存塊就可以再次用于其他目的。
動(dòng)態(tài)分配不幸的是,當(dāng)我們?cè)诰幾g的時(shí)候不知道變量需要多少內(nèi)存的話事情可能就不那么簡單。假設(shè)我們想做下面的事情:
int n = readInput(); // reads input from the user ... // create an array with "n" elements
在此,在編譯階段中,編譯器就沒有辦法知道數(shù)組需要多少內(nèi)存,因?yàn)樗Q于用戶的輸入。
因此,它就不能夠?yàn)闂I系淖兞糠峙淇臻g。相反,我們的程序需要明確地詢問操作運(yùn)行時(shí)需要的空間數(shù)量。這個(gè)內(nèi)存是從堆空間中分配出來的。動(dòng)態(tài)內(nèi)存和靜態(tài)內(nèi)存分配的區(qū)別總結(jié)如下表格:
為了深入地理解動(dòng)態(tài)內(nèi)存分配是如何工作的,我們需要花費(fèi)更多的時(shí)間在指針,這個(gè)可能有點(diǎn)偏離這篇博客的話題。如果你感興趣了解更多,在評(píng)論里面告訴我,我將會(huì)在后續(xù)的博客中挖掘更多的細(xì)節(jié)。
JavaScript中的分配現(xiàn)在我們將解釋JavaScript中的第一步(分配內(nèi)存)。
JavaScript 將開發(fā)者從內(nèi)存分配的處理中解放出來——JavaScript自身可以利用聲明變量來完成這些任務(wù)。
var n = 374; // allocates memory for a number var s = "sessionstack"; // allocates memory for a string var o = { a: 1, b: null }; // allocates memory for an object and its contained values var a = [1, null, "str"]; // (like object) allocates memory for the // array and its contained values function f(a) { return a + 3; } // allocates a function (which is a callable object) // function expressions also allocate an object someElement.addEventListener("click", function() { someElement.style.backgroundColor = "blue"; }, false);
一些函數(shù)調(diào)用也會(huì)導(dǎo)致一些對(duì)象的分配:
var d = new Date(); // allocates a Date object var e = document.createElement("div"); // allocates a DOM element
能夠分配新的值或者對(duì)象的方法:
var s1 = "sessionstack"; var s2 = s1.substr(0, 3); // s2 is a new string // Since strings are immutable, // JavaScript may decide to not allocate memory, // but just store the [0, 3] range. var a1 = ["str1", "str2"]; var a2 = ["str3", "str4"]; var a3 = a1.concat(a2); // new array with 4 elements being // the concatenation of a1 and a2 elements在JavaScript中使用內(nèi)存
基本上在JavaScript中分配內(nèi)存,就意味著在其中讀寫。
這可以通過對(duì)一個(gè)變量或者一個(gè)對(duì)象的屬性甚至是向函數(shù)傳遞一個(gè)參數(shù)來完成。
當(dāng)內(nèi)存不再需要的時(shí)候釋放它大多數(shù)的內(nèi)存管理的問題就來自于這個(gè)階段。
最困難的任務(wù)就是如何知道何時(shí)被分配的不再需要了。它經(jīng)常需要開發(fā)者決定在程序的什么地方某段內(nèi)存不再需要了并且對(duì)其進(jìn)行釋放。
高層次語言內(nèi)嵌了一個(gè)稱為垃圾收集器的軟件,他的任務(wù)就是跟蹤內(nèi)存分配并且用于需找不再需要的分配過的內(nèi)存,并且自動(dòng)地對(duì)其進(jìn)行釋放。
不幸的是,這個(gè)過程是一個(gè)近似,因?yàn)橹朗欠衲硥K內(nèi)存是需要的問題是不可決定的(無法通過算法解決)
大多數(shù)的垃圾收集器通過收集再也無法訪問的內(nèi)存工作,比如:指向它的所有變量都超出了作用域。然而,這依然是對(duì)于可以收集的內(nèi)存空間的預(yù)估,因?yàn)樵谌魏挝恢萌钥赡芤恍┳兞吭谧饔糜騼?nèi)指向這個(gè)內(nèi)存,然而它再也不能被訪問了。
垃圾收集器由于找到一些是“不再需要的”是不可決定的事實(shí),垃圾收集實(shí)現(xiàn)了對(duì)一般問題的解決方案的限制。這一節(jié)將會(huì)解釋理解主要的垃圾收集算法以及它們的限制的需要注意的事項(xiàng)。
內(nèi)存引用垃圾收集算法依賴的主要概念之一就是引用。
在內(nèi)存管理的上下文中,一個(gè)對(duì)象被稱為是對(duì)于另外一個(gè)對(duì)象的引用,如果前者可以訪問后者(隱含或明確的)。例如,一個(gè)JavaScript對(duì)象都有一個(gè)指向其原型的引用(隱含的引用)
在這個(gè)上下文中,“對(duì)象”的概念擴(kuò)展到比普通的JavaScript對(duì)象要廣并且包括函數(shù)作用域(或者全局詞法作用域)。
基于引用計(jì)數(shù)的垃圾收集器詞法作用域定義了變量名稱是如何在嵌套函數(shù)中解析的:內(nèi)部函數(shù)包含了父函數(shù)的作用域即使父函數(shù)已經(jīng)返回了。
這是最簡單的垃圾收集器算法。如果沒有引用指向這個(gè)對(duì)象的時(shí)候,這個(gè)對(duì)象就被認(rèn)為是“可以作為垃圾收集”。
請(qǐng)看如下代碼:
var o1 = { o2: { x: 1 } }; // 2 objects are created. // "o2" is referenced by "o1" object as one of its properties. // None can be garbage-collected var o3 = o1; // the "o3" variable is the second thing that // has a reference to the object pointed by "o1". o1 = 1; // now, the object that was originally in "o1" has a // single reference, embodied by the "o3" variable var o4 = o3.o2; // reference to "o2" property of the object. // This object has now 2 references: one as // a property. // The other as the "o4" variable o3 = "374"; // The object that was originally in "o1" has now zero // references to it. // It can be garbage-collected. // However, what was its "o2" property is still // referenced by the "o4" variable, so it cannot be // freed. o4 = null; // what was the "o2" property of the object originally in // "o1" has zero references to it. // It can be garbage collected.循環(huán)在產(chǎn)生問題
當(dāng)遇到循環(huán)的時(shí)候就會(huì)有一個(gè)限制。在下面的實(shí)例之中,創(chuàng)建兩個(gè)對(duì)象,并且互相引用,因此就會(huì)產(chǎn)生一個(gè)循環(huán)。當(dāng)函數(shù)調(diào)用結(jié)束之后它們會(huì)走出作用域之外,因此它們就沒什么用并且可以被釋放。但是,基于引用計(jì)數(shù)的算法認(rèn)為這兩個(gè)對(duì)象都會(huì)被至少引用一次,所以它倆都不會(huì)被垃圾收集器收集。
function f() { var o1 = {}; var o2 = {}; o1.p = o2; // o1 references o2 o2.p = o1; // o2 references o1. This creates a cycle. } f();標(biāo)記-清除算法
為了決定哪個(gè)對(duì)象是需要的,算法會(huì)決定是否這個(gè)對(duì)象是可訪問的。
這個(gè)算法由以下步驟組成:
這個(gè)垃圾收集器構(gòu)建一個(gè)“roots”列表。Root是全局變量,被代碼中的引用所保存。在 JavaScript中,“window”就是這樣的作為root的全局變量的例子。
所有的root都會(huì)被監(jiān)測(cè)并且被標(biāo)志成活躍的(比如不是垃圾)。所有的子代也會(huì)遞歸地被監(jiān)測(cè)。所有能夠由root訪問的一切都不會(huì)被認(rèn)為是垃圾。
所有不再被標(biāo)志成活躍的內(nèi)存塊都被認(rèn)為是垃圾。這個(gè)收集器現(xiàn)在就可以釋放這些內(nèi)存并將它們返還給操作系統(tǒng)。
這個(gè)算法要優(yōu)于之前的因?yàn)椤耙粋€(gè)具有0引用的對(duì)象”可以讓一個(gè)對(duì)象不能夠再被訪問。但是相反的卻不一定成立,比如我們遇到循環(huán)的時(shí)候。
在2012年,所有的現(xiàn)代瀏覽器都使用標(biāo)記-清除垃圾收集器。過去幾年,JavaScript垃圾收集(代數(shù)/增量/并行/并行垃圾收集)領(lǐng)域的所有改進(jìn)都是對(duì)該算法(標(biāo)記和掃描)的實(shí)現(xiàn)進(jìn)行了改進(jìn),但并沒有對(duì)垃圾收集算法本身的改進(jìn), 其目標(biāo)是確定一個(gè)對(duì)象是否可達(dá)。
在這篇文章中,你可以得到更多關(guān)于垃圾收集追蹤并且也覆蓋到了關(guān)于標(biāo)記-清除算法的優(yōu)化。
循環(huán)不再是一個(gè)問題在上述的第一個(gè)例子中,在函數(shù)調(diào)用返回之后,這兩個(gè)對(duì)象不能夠被全局對(duì)象所訪問。因此,垃圾收集器就會(huì)發(fā)現(xiàn)它們不能夠被訪問了。
即使在這兩個(gè)對(duì)象之間存在著引用,它們?cè)僖膊荒軓膔oot訪問了。
列舉垃圾收集器的直觀行為雖然垃圾收集器很方便,但它們自己也有自己的代價(jià)。 其中一個(gè)是非確定論。 換句話說,GC是不可預(yù)測(cè)的。 你不能真正地告訴你什么時(shí)候會(huì)收集。 這意味著在某些情況下,程序會(huì)使用實(shí)際需要的更多內(nèi)存。 在其他情況下,特別敏感的應(yīng)用程序可能會(huì)引起短暫暫停。 雖然非確定性意味著在執(zhí)行集合時(shí)無法確定,但大多數(shù)GC實(shí)現(xiàn)共享在分配期間執(zhí)行收集遍歷的常見模式。 如果沒有執(zhí)行分配,大多數(shù)GC保持空閑狀態(tài)。 考慮以下情況:
執(zhí)行相當(dāng)大的一組分配。
這些元素中的大多數(shù)(或全部)被標(biāo)記為不可訪問(假設(shè)我們將指向我們不再需要的緩存的引用置空)。
不再執(zhí)行分配。
在這種情況下,大多數(shù)GC不會(huì)再運(yùn)行收集處理。換句話說,即使存在對(duì)于收集器來說不可訪問的引用,它們也不會(huì)被收集器所認(rèn)領(lǐng)。嚴(yán)格意義來說這并不是泄露,但是依然會(huì)導(dǎo)致比平常更多的內(nèi)存使用。
什么是內(nèi)存泄露?實(shí)質(zhì)上,內(nèi)存泄漏可以被定義為應(yīng)用程序不再需要的內(nèi)存,但是由于某些原因不會(huì)返回到操作系統(tǒng)或可用內(nèi)存池。
編程語言有支持管理內(nèi)存的不同方法。 然而,某塊內(nèi)存是否被使用實(shí)際上是一個(gè)不可判定的問題。 換句話說,只有開發(fā)人員可以清楚一個(gè)內(nèi)存是否可以返回到操作系統(tǒng)。
某些編程語言提供了幫助開發(fā)者執(zhí)行此操作的功能。其他的則期望開發(fā)人員能夠完全明確何時(shí)使用一塊內(nèi)存。 維基百科有關(guān)于手動(dòng)和自動(dòng)內(nèi)存管理的好文章。
四種常見的JavaScript泄露 1: 全局變量JavaScript 使用一種有趣的方式處理未聲明的變量:一個(gè)未聲明變量的引用會(huì)在全局對(duì)象內(nèi)部產(chǎn)生一個(gè)新的變量。在瀏覽器的情況,這個(gè)全局變量就會(huì)是window。換句話說:
function foo(arg) { bar = "some text"; }
等同于:
function foo(arg) { window.bar = "some text"; }
如果bar被期望僅僅在foo函數(shù)作用域內(nèi)保持對(duì)變量的引用,并且你忘記使用var去聲明它,一個(gè)意想不到的全局變量就產(chǎn)生了。
在這個(gè)例子中,泄露就僅僅是一個(gè)字符串并不會(huì)帶來太多危害,但是它可能會(huì)變得更糟。
另外一種可能產(chǎn)生意外的全局變量的方式是:
function foo() { this.var1 = "potential accidental global"; } // Foo called on its own, this points to the global object (window) // rather than being undefined. foo();
為了阻止這些錯(cuò)誤的發(fā)生,可以在js文件頭部添加"use strict"。這將會(huì)使用嚴(yán)格模式來解析 JavaScript 從而阻止意外的全局變量。了解更多關(guān)于JavaScript執(zhí)行的模式。
即使我們討論了未預(yù)期的全局變量,但仍然有很多代碼用顯式的全局變量填充。 這些定義是不可收集的(除非分配為null或重新分配)。 特別是,用于臨時(shí)存儲(chǔ)和處理大量信息的全局變量值得關(guān)注。 如果你必須使用全局變量來存儲(chǔ)大量數(shù)據(jù),請(qǐng)確保在完成之后將其分配為null或重新分配。
2: 被遺忘的計(jì)時(shí)器和回調(diào)setInterval 在 JavaScript 中是經(jīng)常被使用的。
大多數(shù)提供觀察者和其他模式的回調(diào)函數(shù)庫都會(huì)在調(diào)用自己的實(shí)例變得無法訪問之后對(duì)其任何引用也設(shè)置為不可訪問。 但是在setInterval的情況下,這樣的代碼很常見:
var serverData = loadData(); setInterval(function() { var renderer = document.getElementById("renderer"); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); //This will be executed every ~5 seconds.
這個(gè)例子說明了計(jì)時(shí)器可能發(fā)生的情況:計(jì)時(shí)器可能會(huì)產(chǎn)生再也不被需要的節(jié)點(diǎn)或者數(shù)據(jù)的引用。
renderer所代表的對(duì)象在未來可能被移除,讓部分interval 處理器中代碼變得不再被需要。然而,這個(gè)處理器不能夠被收集因?yàn)閕nterval依然活躍的(這個(gè)interval需要被停止從而表面這種情況)。如果這個(gè)interval處理器不能夠被收集,那么它的依賴也不能夠被收集。這意味這存儲(chǔ)大量數(shù)據(jù)的severData也不能夠被收集。
在這種觀察者的情況下,做出準(zhǔn)確的調(diào)用從而在不需要它們的時(shí)候立即將其移除是非常重要的(或者相關(guān)的對(duì)象被置為不可訪問的)。
過去,以前特別重要的是某些瀏覽器(好的老IE 6)無法管理好循環(huán)引用(有關(guān)更多信息,請(qǐng)參見下文)。 如今,大多數(shù)瀏覽器一旦觀察到的對(duì)象變得無法訪問,就能收集觀察者處理器,即使偵聽器沒有被明確刪除。 但是,在處理對(duì)象之前,明確刪除這些觀察者仍然是一個(gè)很好的做法。 例如:
var element = document.getElementById("launch-button"); var counter = 0; function onClick(event) { counter++; element.innerHtml = "text " + counter; } element.addEventListener("click", onClick); // Do stuff element.removeEventListener("click", onClick); element.parentNode.removeChild(element); // Now when element goes out of scope, // both element and onClick will be collected even in old browsers // that don"t handle cycles well.
當(dāng)今,現(xiàn)在瀏覽器(報(bào)錯(cuò)IE和Edge)都使用了現(xiàn)代的垃圾收集算法,其能夠檢測(cè)到這些循環(huán)并且進(jìn)行適宜的處理。換句話說,再也不是嚴(yán)格需要在將節(jié)點(diǎn)置為不可訪問之前調(diào)用removeEventListener 。
框架和庫(如jQuery)在處理節(jié)點(diǎn)之前(在為其使用特定的API時(shí))會(huì)刪除偵聽器。 這是由庫內(nèi)部處理的,這也確保沒有泄漏,即使在有問題的瀏覽器下運(yùn)行,如...是的,IE 6。
3: 閉包JavaScript 開發(fā)的一個(gè)關(guān)鍵方面是閉包:一個(gè)可以訪問外部(封閉)函數(shù)變量的內(nèi)部函數(shù)。 由于JavaScript運(yùn)行時(shí)的實(shí)現(xiàn)細(xì)節(jié),可以通過以下方式泄漏內(nèi)存:
var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) // a reference to "originalThing" console.log("hi"); }; theThing = { longStr: new Array(1000000).join("*"), someMethod: function () { console.log("message"); } }; }; setInterval(replaceThing, 1000);
這個(gè)代碼段會(huì)做一件事情:每次 replaceThing 被調(diào)用時(shí),theThing 都會(huì)獲取一個(gè)一個(gè)包含一個(gè)大數(shù)組的以及一個(gè)新的閉包(someMethod)。同時(shí),unused 會(huì)保持一個(gè)指向originalThing引用的閉包(從上一個(gè)調(diào)用的theThing到replaceThing)。可能已經(jīng)很迷惑了,是不是?重要的事情是一旦在相同的父級(jí)作用域?yàn)殚]包產(chǎn)生作用域,這個(gè)作用域就會(huì)被共享。
在這種情況下,為someMethod閉包產(chǎn)生的作用域就會(huì)被unused 所共享。unused 具有對(duì)于originaThing的引用。即使 unused 不再被使用,someMethod依然可以通過replaceThing作用域之外的theThing來使用。并且由于somethod和unused 共享閉包作用域,unused指向originalThing的引用強(qiáng)迫其保持活躍(兩個(gè)閉包之間的整個(gè)共享作用域)。這將會(huì)阻止垃圾手機(jī)。
當(dāng)這個(gè)代碼段重復(fù)運(yùn)行時(shí),可以觀察到內(nèi)存使用量的穩(wěn)定增長。 當(dāng)GC運(yùn)行時(shí),這不會(huì)變小。 實(shí)質(zhì)上,創(chuàng)建了一個(gè)關(guān)閉的鏈接列表(其root以TheThing變量的形式),并且這些閉包的范圍中的每一個(gè)都對(duì)大數(shù)組進(jìn)行間接引用,導(dǎo)致相當(dāng)大的泄漏。
這個(gè)問題由Meteor團(tuán)隊(duì)發(fā)現(xiàn),他們有一篇很好的文章,詳細(xì)描述了這個(gè)問題。
4: DOM 之外的引用有時(shí)將DOM節(jié)點(diǎn)存儲(chǔ)在數(shù)據(jù)結(jié)構(gòu)中可能是有用的。 假設(shè)要快速更新表中的幾行內(nèi)容。 存儲(chǔ)對(duì)字典或數(shù)組中每個(gè)DOM行的引用可能是有意義的。 當(dāng)發(fā)生這種情況時(shí),會(huì)保留對(duì)同一DOM元素的兩個(gè)引用:一個(gè)在DOM樹中,另一個(gè)在字典中。 如果將來某個(gè)時(shí)候您決定刪除這些行,則需要使兩個(gè)引用置為不可訪問。
var elements = { button: document.getElementById("button"), image: document.getElementById("image") }; function doStuff() { image.src = "http://example.com/image_name.png"; } function removeImage() { // The image is a direct child of the body element. document.body.removeChild(document.getElementById("image")); // At this point, we still have a reference to #button in the //global elements object. In other words, the button element is //still in memory and cannot be collected by the GC. }
還有一個(gè)額外的考慮,當(dāng)涉及對(duì)DOM樹內(nèi)部的內(nèi)部或葉節(jié)點(diǎn)的引用時(shí),必須考慮這一點(diǎn)。 假設(shè)你在JavaScript代碼中保留對(duì)表格特定單元格(
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/91912.html
摘要:這是因?yàn)槲覀冊(cè)L問了數(shù)組中不存在的數(shù)組元素它超過了最后一個(gè)實(shí)際分配到內(nèi)存的數(shù)組元素字節(jié),并且有可能會(huì)讀取或者覆寫的位。包含個(gè)元素的新數(shù)組由和數(shù)組元素所組成中的內(nèi)存使用中使用分配的內(nèi)存主要指的是內(nèi)存讀寫。 原文請(qǐng)查閱這里,本文有進(jìn)行刪減,文后增了些經(jīng)驗(yàn)總結(jié)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會(huì)討論日常使用中另一個(gè)被開發(fā)...
摘要:本文作為第三篇,將會(huì)討論另一個(gè)開發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會(huì)提供一些關(guān)于如何處理內(nèi)存泄露的技巧。這是當(dāng)前整型和雙精度的大小。然而,這是一組可以收集的內(nèi)存空間的近似值。 本文轉(zhuǎn)載自:眾成翻譯譯者:Leslie Wang審校: 為之漫筆鏈接:http://www.zcfy.cc/article/4211原文:https://blog.sessionstack.com/how-j...
摘要:本系列的第一篇文章簡單介紹了引擎運(yùn)行時(shí)間和堆棧的調(diào)用。編譯器將插入與操作系統(tǒng)交互的代碼,并申請(qǐng)存儲(chǔ)變量所需的堆棧字節(jié)數(shù)。當(dāng)函數(shù)調(diào)用其他函數(shù)時(shí),每個(gè)函數(shù)在調(diào)用堆棧時(shí)獲得自己的塊。因此,它不能為堆棧上的變量分配空間。 本系列的第一篇文章簡單介紹了引擎、運(yùn)行時(shí)間和堆棧的調(diào)用。第二篇文章研究了谷歌V8 JavaScript引擎的內(nèi)部機(jī)制,并介紹了一些編寫JavaScript代碼的技巧。 在這第...
摘要:本文將會(huì)討論中的內(nèi)存泄漏以及如何處理,方便大家在使用編碼時(shí),更好的應(yīng)對(duì)內(nèi)存泄漏帶來的問題。當(dāng)內(nèi)存不再需要時(shí)進(jìn)行釋放大部分內(nèi)存泄漏問題都是在這個(gè)階段產(chǎn)生的,這個(gè)階段最難的問題就是確定何時(shí)不再需要已分配的內(nèi)存。中的相同對(duì)象稱為全局。 隨著現(xiàn)在的編程語言功能越來越成熟、復(fù)雜,內(nèi)存管理也容易被大家忽略。本文將會(huì)討論JavaScript中的內(nèi)存泄漏以及如何處理,方便大家在使用JavaScript...
閱讀 2022·2023-04-25 23:30
閱讀 1452·2021-11-24 10:18
閱讀 3069·2021-10-09 09:54
閱讀 2017·2021-10-08 10:05
閱讀 3431·2021-09-23 11:21
閱讀 3161·2019-08-30 15:52
閱讀 1560·2019-08-30 13:05
閱讀 1056·2019-08-30 13:02