摘要:本文將會討論中的內(nèi)存泄漏以及如何處理,方便大家在使用編碼時,更好的應對內(nèi)存泄漏帶來的問題。當內(nèi)存不再需要時進行釋放大部分內(nèi)存泄漏問題都是在這個階段產(chǎn)生的,這個階段最難的問題就是確定何時不再需要已分配的內(nèi)存。中的相同對象稱為全局。
隨著現(xiàn)在的編程語言功能越來越成熟、復雜,內(nèi)存管理也容易被大家忽略。本文將會討論JavaScript中的內(nèi)存泄漏以及如何處理,方便大家在使用JavaScript編碼時,更好的應對內(nèi)存泄漏帶來的問題。
概述像C語言這樣的編程語言,具有簡單的內(nèi)存管理功能函數(shù),例如malloc( )和free( )。開發(fā)人員可以使用這些功能函數(shù)來顯式地分配和釋放系統(tǒng)的內(nèi)存。
當創(chuàng)建對象和字符串等時,JavaScript就會分配內(nèi)存,并在不再使用時自動釋放內(nèi)存,這種機制被稱為垃圾收集。這種釋放資源看似是“自動”的,但本質(zhì)是混淆的,這也給JavaScript(以及其他高級語言)的開發(fā)人員產(chǎn)生了可以不關心內(nèi)存管理的錯誤印象。其實這是一個大錯誤。
即使使用高級語言,開發(fā)人員也應該理解內(nèi)存管理的知識。有時自動內(nèi)存管理也會存在問題(例如垃圾收集器中的錯誤或?qū)嵤┫拗频龋?,開發(fā)人員必須了解這些問題才能正確地進行處理。
內(nèi)存生命周期無論你使用的是什么編程語言,內(nèi)存生命周期幾乎都是一樣的:
以下是對內(nèi)存生命周期中每個步驟發(fā)生的情況的概述:
分配內(nèi)存? - 內(nèi)存由操作系統(tǒng)分配,允許程序使用它。在簡單的編程語言中,這個過程是開發(fā)人員應該處理的一個顯式操作。然而,在高級編程語言中,系統(tǒng)會幫助你完成這個操作。
內(nèi)存使用 -? 這是程序使用之前申請內(nèi)存的時間段,你的代碼會通過使用分配的變量來對內(nèi)存進行讀取和寫入操作。
釋放內(nèi)存 ?- 對于不再需要的內(nèi)存進行釋放的操作,以便確保其變成空閑狀態(tài)并且可以被再次使用。與分配內(nèi)存操作一樣,這個操作在簡單的編程語言中是需要顯示操作的。
什么是內(nèi)存?在硬件層面上,計算機的內(nèi)存由大量的觸發(fā)器組成的。每個觸發(fā)器包含一些晶體管,并能夠存儲一位數(shù)據(jù)。多帶帶的觸發(fā)器可以通過唯一的標識符來尋址,所以我們可以讀取和覆蓋它們。因此,從概念上講,我們可以把整個計算機內(nèi)存看作是我們可以讀寫的一大塊空間。
很多東西都存儲在內(nèi)存中:
程序使用的所有變量和其他數(shù)據(jù)。
程序的代碼,包括操作系統(tǒng)的代碼。
編譯器和操作系統(tǒng)一起工作,來處理大部分的內(nèi)存管理,但是我們需要了解從本質(zhì)上發(fā)生了什么。
編譯代碼時,編譯器會檢查原始數(shù)據(jù)類型,并提前計算它們需要多少內(nèi)存,然后將所需的內(nèi)存分配給調(diào)用堆棧空間中的程序。分配這些變量的空間被稱為堆棧空間,隨著函數(shù)的調(diào)用,內(nèi)存會被添加到現(xiàn)有的內(nèi)存之上。當終止時,空間以LIFO(后進先出)順序被移除。例如如下聲明:
int n; // 4個字節(jié) int x [4]; // 4個元素的數(shù)組,每一個占4個字節(jié) double m; // 8個字節(jié)
編譯器插入與操作系統(tǒng)進行交互的代碼,以便在堆棧中請求所需的字節(jié)數(shù)來存儲變量。
在上面的例子中,編譯器知道每個變量的確切內(nèi)存地址。實際上,每當我們寫入這個變量n,它就會在內(nèi)部翻譯成“內(nèi)存地址4127963”。
注意,如果我們試圖訪問x[4],我們將訪問與m關聯(lián)的數(shù)據(jù)。這是因為我們正在訪問數(shù)組中不存在的元素 - 它比數(shù)組中最后一個數(shù)據(jù)實際分配的元素多了4個字節(jié)x[3],并且可能最終讀取(或覆蓋)了一些m比特。這對其余部分會產(chǎn)生不利的后果。
當函數(shù)調(diào)用其它函數(shù)時,每個函數(shù)被調(diào)用時都會得到自己的堆棧塊。它會保留所有的局部變量和一個程序計數(shù)器,還會記錄執(zhí)行的地方。當功能完成時,其內(nèi)存塊會被釋放,可以再次用于其它目的。
動態(tài)分配如若我們不知道編譯時,變量需要的內(nèi)存數(shù)量時,事情就會變得復雜。假設我們想要做如下事項:
int n = readInput(); //讀取用戶的輸入 ... //用“n”個元素創(chuàng)建一個數(shù)組
在編譯時,編譯器不知道數(shù)組需要多少內(nèi)存,因為它是由用戶提供的輸入值決定的。
因此,它不能為堆棧上的變量分配空間。相反,我們的程序需要在運行時明確地向操作系統(tǒng)請求適當?shù)目臻g。這個內(nèi)存是從堆空間分配的。下表總結(jié)了靜態(tài)和動態(tài)內(nèi)存分配之間的區(qū)別:
在JavaScript中分配內(nèi)存現(xiàn)在來解釋如何在JavaScript中分配內(nèi)存。
JavaScript使得開發(fā)人員免于處理內(nèi)存分配的工作。
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)用也會導致對象分配:
var d = new Date(); // allocates a Date object var e = document.createElement("div"); // allocates a DOM element
方法可以分配新的值或?qū)ο螅?/p>
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)存,意味著在其中讀寫。
這可以通過讀取或?qū)懭胱兞炕驅(qū)ο髮傩缘闹?,或者甚至將參?shù)傳遞給函數(shù)來完成。
當內(nèi)存不再需要時進行釋放大部分內(nèi)存泄漏問題都是在這個階段產(chǎn)生的,這個階段最難的問題就是確定何時不再需要已分配的內(nèi)存。它通常需要開發(fā)人員確定程序中的哪個部分不再需要這些內(nèi)存,并將其釋放。
高級語言嵌入了一個名為垃圾收集器的功能,其工作是跟蹤內(nèi)存分配和使用情況,以便在不再需要分配內(nèi)存的情況下自動釋放內(nèi)存。
不幸的是,這個過程無法做到那么準確,因為像某些內(nèi)存不再需要的問題是不能由算法來解決的。
大多數(shù)垃圾收集器通過收集不能被訪問的內(nèi)存來工作,例如指向它的變量超出范圍的這種情況。然而,這種方式只能收集內(nèi)存空間的近似值,因為在內(nèi)存的某些位置可能仍然有指向它的變量,但它卻不會被再次訪問。
由于確定一些內(nèi)存是否“不再需要”,是不可判定的,所以垃圾收集機制就有一定的局限性。下面將解釋主要垃圾收集算法及其局限性的概念。
內(nèi)存引用垃圾收集算法所依賴的主要概念之一就是內(nèi)存引用。
在內(nèi)存管理情況下,如果一個對象訪問變量(可以是隱含的或顯式的),則稱該對象引用另一個對象。例如,JavaScript對象具有對其原對象(隱式引用)及其屬性值(顯式引用)的引用。
在這種情況下,“對象”的概念擴展到比普通JavaScript對象更廣泛的范圍,并且還包含函數(shù)范圍。
引用計數(shù)垃圾收集這是最簡單的垃圾收集算法。如果有零個引用指向它,則該對象會被認為是“垃圾收集” 。
看看下面的代碼:
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.周期引起問題
在周期方面有一個限制。例如下面的例子,創(chuàng)建兩個對象并相互引用,這樣會創(chuàng)建一個循環(huán)引用。在函數(shù)調(diào)用之后,它們將超出范圍,所以它們實際上是無用的,可以被釋放。然而,引用計數(shù)算法認為,由于兩個對象中的每一個都被引用至少一次,所以兩者都不能被垃圾收集機制收回。
function f() { var o1 = {}; var o2 = {}; o1.p = o2; // o1 references o2 o2.p = o1; // o2 references o1. This creates a cycle. } f( );標記和掃描算法
為了決定是否需要對象,標記和掃描算法會確定對象是否是活動的。
標記和掃描算法經(jīng)過以下3個步驟:
roots:通常,root是代碼中引用的全局變量。例如,在JavaScript中,可以充當root的全局變量是“窗口”對象。Node.js中的相同對象稱為“全局”。所有root的完整列表由垃圾收集器構(gòu)建。
然后算法會檢查所有root和他們的子對象并且標記它們是活動的(即它們不是垃圾)。任何root不能達到的,將被標記為垃圾。
最后,垃圾回收器釋放所有未標記為活動的內(nèi)存塊,并將該內(nèi)存返回給操作系統(tǒng)。
這個算法比引用計數(shù)垃圾收集算法更好。JavaScript垃圾收集(代碼/增量/并發(fā)/并行垃圾收集)領域中所做的所有改進都是對這種標記和掃描算法的實現(xiàn)改進,但不是對垃圾收集算法本身的改進。
周期不再是問題了在上面的相互引用例子中,在函數(shù)調(diào)用返回之后,兩個對象不再被全局對象可訪問的對象引用。因此,它們將被垃圾收集器發(fā)現(xiàn),從而進行收回。
即使在對象之間有引用,它們也不能從root目錄中訪問,從而會被認為是垃圾而收集。
抵制垃圾收集器的直觀行為盡管垃圾收集器使用起來很方便,但它們也有自己的一套標準,其中之一是非決定論。換句話說,垃圾收集是不可預測的。你不能真正知道什么時候進行收集,這意味著在某些情況下,程序會使用更多的內(nèi)存,雖然這是實際需要的。在其它情況下,在特別敏感的應用程序中,短暫暫停是很可能出現(xiàn)的。盡管非確定性意味著不能確定何時進行集合,但大多數(shù)垃圾收集實現(xiàn)了共享在分配期間進行收集的通用模式。如果沒有執(zhí)行分配,大多數(shù)垃圾收集會保持空閑狀態(tài)。如以下情況:
大量的分配被執(zhí)行。
大多數(shù)這些元素(或所有這些元素)被標記為無法訪問(假設我們將一個引用指向不再需要的緩存)。
沒有進一步的分配執(zhí)行。
在這種情況下,大多數(shù)垃圾收集不會做出任何的收集工作。換句話說,即使有不可用的引用需要收集,但是收集器不會進行收集。雖然這并不是嚴格的泄漏,但仍會導致內(nèi)存使用率高于平時。
什么是內(nèi)存泄漏?內(nèi)存泄漏是應用程序使用過的內(nèi)存片段,在不再需要時,不能返回到操作系統(tǒng)或可用內(nèi)存池中的情況。
編程語言有各自不同的內(nèi)存管理方式。但是是否使用某一段內(nèi)存,實際上是一個不可判定的問題。換句話說,只有開發(fā)人員明確的知道是否需要將一塊內(nèi)存返回給操作系統(tǒng)。
四種常見的JavaScript內(nèi)存泄漏1:全局變量
JavaScript以一種有趣的方式來處理未聲明的變量:當引用未聲明的變量時,會在全局對象中創(chuàng)建一個新變量。在瀏覽器中,全局對象將是window,這意味著
function foo(arg) { bar = "some text"; }
相當于:
function foo(arg) { window.bar = "some text"; }
bar只是foo函數(shù)中引用一個變量。如果你不使用var聲明,將會創(chuàng)建一個多余的全局變量。在上述情況下,不會造成很大的問題。但是,如若是下面的這種情況。
你也可能不小心創(chuàng)建一個全局變量this:
function foo() { this.var1 = "potential accidental global"; } // Foo called on its own, this points to the global object (window) // rather than being undefined. foo( );
你可以通過在JavaScript文件的開始處添加‘use strict’;來避免這中錯誤,這種方式將開啟嚴格的解析JavaScript模式,從而防止意外創(chuàng)建全局變量。
意外的全局變量當然是一個問題。更多的時候,你的代碼會受到顯式的全局變量的影響,而這些全局變量在垃圾收集器中是無法收集的。需要特別注意用于臨時存儲和處理大量信息的全局變量。如果必須使用全局變量來存儲數(shù)據(jù),那么確保將其分配為空值,或者在完成后重新分配。
2:被遺忘的定時器或回調(diào)
下面列舉setInterval的例子,這也是經(jīng)常在JavaScript中使用。
對于提供監(jiān)視的庫和其它接受回調(diào)的工具,通常在確保所有回調(diào)的引用在其實例無法訪問時,會變成無法訪問的狀態(tài)。但是下面的代碼卻是一個例外:
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.
上面的代碼片段顯示了使用引用節(jié)點或不再需要的數(shù)據(jù)的定時器的結(jié)果。
該renderer對象可能會在某些時候被替換或刪除,這會使interval處理程序封裝的塊變得冗余。如果發(fā)生這種情況,那么處理程序及其依賴項都不會被收集,因為interval需要先停止。這一切都歸結(jié)為存儲和處理負載數(shù)據(jù)的serverData不會被收集的原因。
當使用監(jiān)視器時,你需要確保做了一個明確的調(diào)用來刪除它們。
幸運的是,大多數(shù)現(xiàn)代瀏覽器都會為你做這件事:即使你忘記刪除監(jiān)聽器,當被監(jiān)測對象變得無法訪問,它們就會自動收集監(jiān)測處理器。這是過去的一些瀏覽器無法處理的情況(例如舊的IE6)。
看下面的例子:
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.
由于現(xiàn)代瀏覽器支持垃圾回收機制,所以當某個節(jié)點變的不能訪問時,你不再需要調(diào)用removeEventListener,因為垃圾回收機制會恰當?shù)奶幚磉@些節(jié)點。
如果你正在使用jQueryAPI(其他庫和框架也支持這一點),那么也可以在節(jié)點不用之前刪除監(jiān)聽器。即使應用程序在較舊的瀏覽器版本下運行,庫也會確保沒有內(nèi)存泄漏。
3:閉包
JavaScript開發(fā)的一個關鍵方面是閉包。閉包是一個內(nèi)部函數(shù),可以訪問外部(封閉)函數(shù)的變量。由于JavaScript運行時的實現(xiàn)細節(jié),可能存在以下形式泄漏內(nèi)存:
var theThing = null; var replaceThing = function(){ var originalThing = theThing; var unused = function(){ if(originalThing)//對"originalThing"的引用 console.log(“hi”); }; theThing = { longStr:new Array(1000000).join("*"), someMethod:function(){ console.log(“message”); } }; }; setInterval(replaceThing,1000);
一旦replaceThing被調(diào)用,theThing會獲取由一個大數(shù)組和一個新的閉包(someMethod)組成的新對象。然而,originalThing會被unused變量所持有的閉包所引用(這是theThing從以前的調(diào)用變量replaceThing)。需要記住的是,一旦在同一父作用域中為閉包創(chuàng)建了閉包的作用域,作用域就被共享了。
在這種情況下,閉包創(chuàng)建的范圍會將someMethod共享給unused。然而,unused有一個originalThing引用。即使unused從未使用過,someMethod 也可以通過theThing在整個范圍之外使用replaceThing。而且someMethod通過unused共享了閉包范圍,unused必須引用originalThing以便使其它保持活躍(兩封閉之間的整個共享范圍)。這就阻止了它被收集。
所有這些都可能導致相當大的內(nèi)存泄漏。當上面的代碼片段一遍又一遍地運行時,你會看到內(nèi)存使用率的不斷上升。當垃圾收集器運行時,其內(nèi)存大小不會縮小。這種情況會創(chuàng)建一個閉包的鏈表,并且每個閉包范圍都帶有對大數(shù)組的間接引用。
4:超出DOM引用
在某些情況下,開發(fā)人員會在數(shù)據(jù)結(jié)構(gòu)中存儲DOM節(jié)點,例如你想快速更新表格中的幾行內(nèi)容的情況。如果在字典或數(shù)組中存儲對每個DOM行的引用,則會有兩個對同一個DOM元素的引用:一個在DOM樹中,另一個在字典中。如果你不再需要這些行,則需要使兩個引用都無法訪問。
var elements = { button: document.getElementById("button"), image: document.getElementById("image") }; function doStuff() { elements.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. }
在涉及DOM樹內(nèi)的內(nèi)部節(jié)點或葉節(jié)點時,還有一個額外的因素需要考慮。如果你在代碼中保留對表格單元格(標簽)的引用,并決定從DOM中刪除該表格,還需要保留對該特定單元格的引用,則可能會出現(xiàn)嚴重的內(nèi)存泄漏。你可能會認為垃圾收集器會釋放除了那個單元之外的所有東西,但情況并非如此。由于單元格是表格的一個子節(jié)點,并且子節(jié)點保留著對父節(jié)點的引用,所以對表格單元格的這種引用,會將整個表格保存在內(nèi)存中。
JavaScript 開發(fā)工具推薦SpreadJS 純前端表格控件是基于 HTML5 的 JavaScript 電子表格和網(wǎng)格功能控件,提供了完備的公式引擎、排序、過濾、輸入控件、數(shù)據(jù)可視化、Excel 導入/導出等功能,適用于 .NET、Java 和移動端等各平臺在線編輯類 Excel 功能的表格程序開發(fā)。
總結(jié)以上內(nèi)容是對JavaScript內(nèi)存管理機制的講解,以及常見的四種內(nèi)存泄漏的分析。希望對JavaScript的編程人員有所幫助。
原文鏈接:https://blog.sessionstack.com...
轉(zhuǎn)載請注明出自:葡萄城控件
關于葡萄城葡萄城成立于1980年,是全球最大的控件提供商,世界領先的企業(yè)應用定制工具、企業(yè)報表和商業(yè)智能解決方案提供商,為超過75%的全球財富500強企業(yè)提供服務。葡萄城于1988年在中國設立研發(fā)中心,在全球化產(chǎn)品的研發(fā)過程中,不斷適應中國市場的本地需求,并為軟件企業(yè)和各行業(yè)的信息化提供優(yōu)秀的軟件工具和咨詢服務。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/89892.html
摘要:是如何工作的內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏原文譯者幾個禮拜之前我們開始一系列對于以及其本質(zhì)工作原理的深入挖掘我們認為通過了解的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應用。 JavaScript是如何工作的:內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏 原文:How JavaScript works: memory management + how to han...
摘要:本系列的第一篇文章簡單介紹了引擎運行時間和堆棧的調(diào)用。編譯器將插入與操作系統(tǒng)交互的代碼,并申請存儲變量所需的堆棧字節(jié)數(shù)。當函數(shù)調(diào)用其他函數(shù)時,每個函數(shù)在調(diào)用堆棧時獲得自己的塊。因此,它不能為堆棧上的變量分配空間。 本系列的第一篇文章簡單介紹了引擎、運行時間和堆棧的調(diào)用。第二篇文章研究了谷歌V8 JavaScript引擎的內(nèi)部機制,并介紹了一些編寫JavaScript代碼的技巧。 在這第...
摘要:正式發(fā)布已正式發(fā)布,新版本重點關注工具鏈以及工具鏈在中的運行速度問題。文章內(nèi)容包括什么是內(nèi)存,內(nèi)存生命周期,中的內(nèi)存分配,內(nèi)存釋放,垃圾收集,種常見的內(nèi)存泄漏以及如何處理內(nèi)存泄漏的技巧。 1. Angular 6 正式發(fā)布 Angular 6.0.0 已正式發(fā)布,新版本重點關注工具鏈以及工具鏈在 Angular 中的運行速度問題。Angular v6 是統(tǒng)一整體框架、Material ...
摘要:正式發(fā)布已正式發(fā)布,新版本重點關注工具鏈以及工具鏈在中的運行速度問題。文章內(nèi)容包括什么是內(nèi)存,內(nèi)存生命周期,中的內(nèi)存分配,內(nèi)存釋放,垃圾收集,種常見的內(nèi)存泄漏以及如何處理內(nèi)存泄漏的技巧。 1. Angular 6 正式發(fā)布 Angular 6.0.0 已正式發(fā)布,新版本重點關注工具鏈以及工具鏈在 Angular 中的運行速度問題。Angular v6 是統(tǒng)一整體框架、Material ...
閱讀 1004·2021-11-17 09:33
閱讀 472·2019-08-30 11:16
閱讀 2532·2019-08-29 16:05
閱讀 3409·2019-08-29 15:28
閱讀 1457·2019-08-29 11:29
閱讀 2006·2019-08-26 13:51
閱讀 3446·2019-08-26 11:55
閱讀 1272·2019-08-26 11:31