国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript如何工作:內(nèi)存管理+如何處理4個常見的內(nèi)存泄漏

anRui / 1118人閱讀

摘要:本系列的第一篇文章簡單介紹了引擎運行時間和堆棧的調(diào)用。編譯器將插入與操作系統(tǒng)交互的代碼,并申請存儲變量所需的堆棧字節(jié)數(shù)。當(dāng)函數(shù)調(diào)用其他函數(shù)時,每個函數(shù)在調(diào)用堆棧時獲得自己的塊。因此,它不能為堆棧上的變量分配空間。

本系列的第一篇文章簡單介紹了引擎、運行時間和堆棧的調(diào)用。第二篇文章研究了谷歌V8 JavaScript引擎的內(nèi)部機制,并介紹了一些編寫JavaScript代碼的技巧。

在這第三篇文章中,我們將討論另一個重要主題——內(nèi)存管理,這是由于日常使用的編程語言越來越成熟和復(fù)雜,開發(fā)人員容易忽視這一問題。我們還將提供一些有關(guān)如何處理JavaScript中的內(nèi)存泄漏的技巧,在SessionStack中遵循這些技巧,既能確保SessionStack 不會導(dǎo)致內(nèi)存泄漏,也不會增加我們集成的Web應(yīng)用程序的內(nèi)存消耗。

想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你!

概述

像 C 這樣的編程語言,具有低級內(nèi)存管理原語,如malloc()和free()。開發(fā)人員使用這些原語顯式地對操作系統(tǒng)的內(nèi)存進行分配和釋放。

而JavaScript在創(chuàng)建對象(對象、字符串等)時會為它們分配內(nèi)存,不再使用對時會“自動”釋放內(nèi)存,這個過程稱為垃圾收集。這種看“自動”似釋放資源的的特性是造成混亂的根源,因為這給JavaScript(和其他高級語言)開發(fā)人員帶來一種錯覺,以為他們可以不關(guān)心內(nèi)存管理的錯誤印象,這是想法一個大錯誤。

即使在使用高級語言時,開發(fā)人員也應(yīng)該了解內(nèi)存管理(或者至少懂得一些基礎(chǔ)知識)。有時候,自動內(nèi)存管理存在一些問題(例如垃圾收集器中的bug或?qū)崿F(xiàn)限制等),開發(fā)人員必須理解這些問題,以便可以正確地處理它們(或者找到一個適當(dāng)?shù)慕鉀Q方案,以最小代價來維護代碼)。

內(nèi)存的生命周期

無論使用哪種編程語言,內(nèi)存的生命周期都是一樣的:

這里簡單介紹一下內(nèi)存生命周期中的每一個階段:

分配內(nèi)存 —? 內(nèi)存是由操作系統(tǒng)分配的,它允許您的程序使用它。在低級語言(例如C語言)中,這是一個開發(fā)人員需要自己處理的顯式執(zhí)行的操作。然而,在高級語言中,系統(tǒng)會自動為你分配內(nèi)在。

使用內(nèi)存 — 這是程序?qū)嶋H使用之前分配的內(nèi)存,在代碼中使用分配的變量時,就會發(fā)生讀和寫操作。

釋放內(nèi)存 — 釋放所有不再使用的內(nèi)存,使之成為自由內(nèi)存,并可以被重利用。與分配內(nèi)存操作一樣,這一操作在低級語言中也是需要顯式地執(zhí)行。

內(nèi)存是什么?

在介紹JavaScript中的內(nèi)存之前,我們將簡要討論內(nèi)存是什么以及它是如何工作的。

硬件層面上,計算機內(nèi)存由大量的觸發(fā)器緩存的。每個觸發(fā)器包含幾個晶體管,能夠存儲一位,單個觸發(fā)器都可以通過唯一標(biāo)識符尋址,因此我們可以讀取和覆蓋它們。因此,從概念上講,可以把的整個計算機內(nèi)存看作是一個可以讀寫的巨大數(shù)組。

作為人類,我們并不擅長用比特來思考和計算,所以我們把它們組織成更大的組,這些組一起可以用來表示數(shù)字。8位稱為1字節(jié)。除了字節(jié),還有字(有時是16位,有時是32位)。

很多東西都存儲在內(nèi)存中:

程序使用的所有變量和其他數(shù)據(jù)。

程序的代碼,包括操作系統(tǒng)的代碼。

編譯器和操作系統(tǒng)一起為你處理大部分內(nèi)存管理,但是你還是需要了解一下底層的情況,對內(nèi)在管理概念會有更深入的了解。

在編譯代碼時,編譯器可以檢查基本數(shù)據(jù)類型,并提前計算它們需要多少內(nèi)存。然后將所需的大小分配給調(diào)用堆棧空間中的程序,分配這些變量的空間稱為堆棧空間。因為當(dāng)調(diào)用函數(shù)時,它們的內(nèi)存將被添加到現(xiàn)有內(nèi)存之上,當(dāng)它們終止時,它們按照后進先出(LIFO)順序被移除。例如:

編譯器能夠立即知道所需的內(nèi)存:4 + 4×4 + 8 = 28字節(jié)。

這段代碼展示了整型和雙精度浮點型變量所占內(nèi)存的大小。但是大約20年前,整型變量通常占2個字節(jié),而雙精度浮點型變量占4個字節(jié)。你的代碼不應(yīng)該依賴于當(dāng)前基本數(shù)據(jù)類型的大小。

編譯器將插入與操作系統(tǒng)交互的代碼,并申請存儲變量所需的堆棧字節(jié)數(shù)。

在上面的例子中,編譯器知道每個變量的確切內(nèi)存地址。事實上,每當(dāng)我們寫入變量 n 時,它就會在內(nèi)部被轉(zhuǎn)換成類似“內(nèi)存地址4127963”這樣的信息。

注意,如果我們嘗試訪問 x[4],將訪問與m關(guān)聯(lián)的數(shù)據(jù)。這是因為訪問數(shù)組中一個不存在的元素(它比數(shù)組中最后一個實際分配的元素x[3]多4字節(jié)),可能最終讀取(或覆蓋)一些 m 位。這肯定會對程序的其余部分產(chǎn)生不可預(yù)知的結(jié)果。

當(dāng)函數(shù)調(diào)用其他函數(shù)時,每個函數(shù)在調(diào)用堆棧時獲得自己的塊。它保存所有的局部變量,但也會有一個程序計數(shù)器來記住它在執(zhí)行過程中的位置。當(dāng)函數(shù)完成時,它的內(nèi)存塊將再次用于其他地方。

動態(tài)分配

不幸的是,當(dāng)編譯時不知道一個變量需要多少內(nèi)存時,事情就有點復(fù)雜了。假設(shè)我們想做如下的操作:

在編譯時,編譯器不知道數(shù)組需要使用多少內(nèi)存,因為這是由用戶提供的值決定的。

因此,它不能為堆棧上的變量分配空間。相反,我們的程序需要在運行時顯式地向操作系統(tǒng)請求適當(dāng)?shù)目臻g,這個內(nèi)存是從堆空間分配的。靜態(tài)內(nèi)存分配和動態(tài)內(nèi)存分配的區(qū)別總結(jié)如下表所示:

靜態(tài)內(nèi)存分配 動態(tài)內(nèi)存分配
大小必須在編譯時知道 大小不需要在編譯時知道
在編譯時執(zhí)行 在運行時執(zhí)行
分配給堆棧 分配給堆
FILO (先進后出) 沒有特定的分配順序

要完全理解動態(tài)內(nèi)存分配是如何工作的,需要在指針上花費更多的時間,這可能與本文的主題有太多的偏離,這里就不太詳細(xì)介紹指針的相關(guān)的知識了。

在JavaScript中分配內(nèi)存

現(xiàn)在將解釋第一步:如何在JavaScript中分配內(nèi)存。

JavaScript為讓開發(fā)人員免于手動處理內(nèi)存分配的責(zé)任——JavaScript自己進行內(nèi)存分配同時聲明值。

某些函數(shù)調(diào)用也會導(dǎo)致對象的內(nèi)存分配:

方法可以分配新的值或?qū)ο?

在JavaScript中使用內(nèi)存

在JavaScript中使用分配的內(nèi)存意味著在其中讀寫,這可以通過讀取或?qū)懭胱兞炕驅(qū)ο髮傩缘闹担蛘邔?shù)傳遞給函數(shù)來實現(xiàn)。

當(dāng)內(nèi)存不再需要時進行釋放
大多數(shù)的內(nèi)存管理問題都出現(xiàn)在這個階段

這里最困難的地方是確定何時不再需要分配的內(nèi)存,它通常要求開發(fā)人員確定程序中哪些地方不再需要內(nèi)存的并釋放它。

高級語言嵌入了一種稱為垃圾收集器的機制,它的工作是跟蹤內(nèi)存分配和使用,以便發(fā)現(xiàn)任何時候一塊不再需要已分配的內(nèi)在。在這種情況下,它將自動釋放這塊內(nèi)存。

不幸的是,這個過程只是進行粗略估計,因為很難知道某塊內(nèi)存是否真的需要 (不能通過算法來解決)。

大多數(shù)垃圾收集器通過收集不再被訪問的內(nèi)存來工作,例如,指向它的所有變量都超出了作用域。但是,這是可以收集的內(nèi)存空間集合的一個不足估計值,因為在內(nèi)存位置的任何一點上,仍然可能有一個變量在作用域中指向它,但是它將永遠(yuǎn)不會被再次訪問。

垃圾收集

由于無法確定某些內(nèi)存是否真的有用,因此,垃圾收集器想了一個辦法來解決這個問題。本節(jié)將解釋理解主要垃圾收集算法及其局限性。

內(nèi)存引用

垃圾收集算法主要依賴的是引用。

在內(nèi)存管理上下文中,如果對象具有對另一個對象的訪問權(quán)(可以是隱式的,也可以是顯式的),則稱對象引用另一個對象。例如,JavaScript對象具有對其原型(隱式引用)和屬性值(顯式引用)的引用。

在此上下文中,“對象”的概念被擴展到比常規(guī)JavaScript對象更廣泛的范圍,并且還包含函數(shù)范圍(或全局詞法作用域)。

詞法作用域定義了如何在嵌套函數(shù)中解析變量名:即使父函數(shù)已經(jīng)返回,內(nèi)部函數(shù)也包含父函數(shù)的作用
引用計數(shù)垃圾收集算法

這是最簡單的垃圾收集算法。如果沒有指向?qū)ο蟮囊茫瑒t認(rèn)為該對象是“垃圾可回收的”,如下代碼:

循環(huán)會產(chǎn)生問題

當(dāng)涉及到循環(huán)時,會有一個限制。在下面的示例中,創(chuàng)建了兩個對象,兩個對象互相引用,從而創(chuàng)建了一個循環(huán)。在函數(shù)調(diào)用之后將超出作用域,因此它們實際上是無用的,可以被釋放。然而,引用計數(shù)算法認(rèn)為,由于每個對象至少被引用一次,所以它們都不能被垃圾收集。

標(biāo)記-清除(Mark-and-sweep)算法

該算法能夠判斷出某個對象是否可以訪問,從而知道該對象是否有用,該算法由以下步驟組成:

垃圾收集器構(gòu)建一個“根”列表,用于保存引用的全局變量。在JavaScript中,“window”對象是一個可作為根節(jié)點的全局變量。

然后,算法檢查所有根及其子節(jié)點,并將它們標(biāo)記為活動的(這意味著它們不是垃圾)。任何根不能到達(dá)的地方都將被標(biāo)記為垃圾。

最后,垃圾收集器釋放所有未標(biāo)記為活動的內(nèi)存塊,并將該內(nèi)存返回給操作系統(tǒng)。

這個算法比上一個算法要好,因為“一個對象沒有被引用”就意味著這個對象無法訪問。

截至2012年,所有現(xiàn)代瀏覽器都有標(biāo)記-清除垃圾收集器。過去幾年在JavaScript垃圾收集(分代/增量/并發(fā)/并行垃圾收集)領(lǐng)域所做的所有改進都是對該算法(標(biāo)記-清除)的實現(xiàn)改進,而不是對垃圾收集算法本身的改進,也不是它決定對象是否可訪問的目標(biāo)。

在這篇文章中,你可以更詳細(xì)地閱讀到有關(guān)跟蹤垃圾收集的詳細(xì)信息,同時還包括了標(biāo)記-清除算法及其優(yōu)化。

循環(huán)不再是問題

在上面的第一個例子中,在函數(shù)調(diào)用返回后,這兩個對象不再被從全局對象中可訪問的對象引用。因此,垃圾收集器將發(fā)現(xiàn)它們不可訪問。

盡管對象之間存在引用,但它們對于根節(jié)點來說是不可達(dá)的。

垃圾收集器的反直觀行為

盡管垃圾收集器很方便,但它們有一套自己的折衷方案,其中之一就是非決定論,換句話說,GC是不可預(yù)測的,你無法真正判斷何時進行垃圾收集。這意味著在某些情況下,程序會使用更多的內(nèi)存,這實際上是必需的。在對速度特別敏感的應(yīng)用程序中,可能會很明顯的感受到短時間的停頓。如果沒有分配內(nèi)存,則大多數(shù)GC將處于空閑狀態(tài)。看看以下場景:

分配一組相當(dāng)大的內(nèi)在。

這些元素中的大多數(shù)(或全部)被標(biāo)記為不可訪問(假設(shè)引用指向一個不再需要的緩存)。

不再進一步的分配

在這些場景中,大多數(shù)GCs 將不再繼續(xù)收集。換句話說,即使有不可訪問的引用可供收集,收集器也不會聲明這些引用。這些并不是嚴(yán)格意義上的泄漏,但仍然會導(dǎo)致比通常更高的內(nèi)存使用。

內(nèi)存泄漏是什么?

從本質(zhì)上說,內(nèi)存泄漏可以定義為:不再被應(yīng)用程序所需要的內(nèi)存,出于某種原因,它不會返回到操作系統(tǒng)或空閑內(nèi)存池中。

編程語言支持不同的內(nèi)存管理方式。然而,是否使用某一塊內(nèi)存實際上是一個無法確定的問題。換句話說,只有開發(fā)人員才能明確一塊內(nèi)存是否可以返回到操作系統(tǒng)。

某些編程語言為開發(fā)人員提供了幫助,另一些則期望開發(fā)人員能清楚地了解內(nèi)存何時不再被使用。維基百科上有一些有關(guān)人工和自動內(nèi)存管理的很不錯的文章。

四種常見的內(nèi)存泄漏 1.全局變量

JavaScript以一種有趣的方式處理未聲明的變量: 對于未聲明的變量,會在全局范圍中創(chuàng)建一個新的變量來對其進行引用。在瀏覽器中,全局對象是window。例如:

function foo(arg) {
    bar = "some text";
}

等價于:

function foo(arg) {
    window.bar = "some text";
}

如果bar在foo函數(shù)的作用域內(nèi)對一個變量進行引用,卻忘記使用var來聲明它,那么將創(chuàng)建一個意想不到的全局變量。在這個例子中,遺漏一個簡單的字符串不會造成太大的危害,但這肯定會很糟。

創(chuàng)建一個意料之外的全局變量的另一種方法是使用this:

function foo() {
    this.var1 = "potential accidental global";
}
// Foo自己調(diào)用,它指向全局對象(window),而不是未定義。
foo();
可以在JavaScript文件的開頭通過添加“use strict”來避免這一切,它將開啟一個更嚴(yán)格的JavaScript解析模式,以防止意外創(chuàng)建全局變量。

盡管我們討論的是未知的全局變量,但仍然有很多代碼充斥著顯式的全局變量。根據(jù)定義,這些是不可收集的(除非被指定為空或重新分配)。用于臨時存儲和處理大量信息的全局變量特別令人擔(dān)憂。如果你必須使用一個全局變量來存儲大量數(shù)據(jù),那么請確保將其指定為null,或者在完成后將其重新賦值。

2.被遺忘的定時器和回調(diào)

setInterval為例,因為它在JavaScript中經(jīng)常使用。

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById("renderer");
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //每五秒會執(zhí)行一次

上面的代碼片段演示了使用定時器時引用不再需要的節(jié)點或數(shù)據(jù)。

renderer表示的對象可能會在未來的某個時間點被刪除,從而導(dǎo)致內(nèi)部處理程序中的一整塊代碼都變得不再需要。但是,由于定時器仍然是活動的,所以,處理程序不能被收集,并且其依賴項也無法被收集。這意味著,存儲著大量數(shù)據(jù)的serverData也不能被收集。

在使用觀察者時,您需要確保在使用完它們之后進行顯式調(diào)用來刪除它們(要么不再需要觀察者,要么對象將變得不可訪問)。

作為開發(fā)者時,需要確保在完成它們之后進行顯式刪除它們(或者對象將無法訪問)。

在過去,一些瀏覽器無法處理這些情況(很好的IE6)。幸運的是,現(xiàn)在大多數(shù)現(xiàn)代瀏覽器會為幫你完成這項工作:一旦觀察到的對象變得不可訪問,即使忘記刪除偵聽器,它們也會自動收集觀察者處理程序。然而,我們還是應(yīng)該在對象被處理之前顯式地刪除這些觀察者。例如:

如今,現(xiàn)在的瀏覽器(包括IE和Edge)使用現(xiàn)代的垃圾回收算法,可以立即發(fā)現(xiàn)并處理這些循環(huán)引用。換句話說,在一個節(jié)點刪除之前也不是必須要調(diào)用removeEventListener。

一些框架或庫,比如JQuery,會在處置節(jié)點之前自動刪除監(jiān)聽器(在使用它們特定的API的時候)。這是由庫內(nèi)部的機制實現(xiàn)的,能夠確保不發(fā)生內(nèi)存泄漏,即使在有問題的瀏覽器下運行也能這樣,比如……IE 6。

3.閉包

閉包是javascript開發(fā)的一個關(guān)鍵方面,一個內(nèi)部函數(shù)使用了外部(封閉)函數(shù)的變量。由于JavaScript運行的細(xì)節(jié),它可能以下面的方式造成內(nèi)存泄漏:

這段代碼做了一件事:每次調(diào)用replaceThing的時候,theThing都會得到一個包含一個大數(shù)組和一個新閉包(someMethod)的新對象。同時,變量unused指向一個引用了`originalThing的閉包。

是不是有點困惑了? 重要的是,一旦具有相同父作用域的多個閉包的作用域被創(chuàng)建,則這個作用域就可以被共享。

在這種情況下,為閉包someMethod而創(chuàng)建的作用域可以被unused共享的。unused內(nèi)部存在一個對originalThing的引用。即使unused從未使用過,someMethod也可以在replaceThing的作用域之外(例如在全局范圍內(nèi))通過theThing來被調(diào)用。

由于someMethod共享了unused閉包的作用域,那么unused引用包含的originalThing會迫使它保持活動狀態(tài)(兩個閉包之間的整個共享作用域)。這阻止了它被收集。

當(dāng)這段代碼重復(fù)運行時,可以觀察到內(nèi)存使用在穩(wěn)定增長,當(dāng)GC運行后,內(nèi)存使用也不會變小。從本質(zhì)上說,在運行過程中創(chuàng)建了一個閉包鏈表(它的根是以變量theThing的形式存在),并且每個閉包的作用域都間接引用了了一個大數(shù)組,這造成了相當(dāng)大的內(nèi)存泄漏。

4.脫離DOM的引用

有時,將DOM節(jié)點存儲在數(shù)據(jù)結(jié)構(gòu)中可能會很有用。假設(shè)你希望快速地更新表中的幾行內(nèi)容,那么你可以在一個字典或數(shù)組中保存每個DOM行的引用。這樣,同一個DOM元素就存在兩個引用:一個在DOM樹中,另一個則在字典中。如果在將來的某個時候你決定刪除這些行,那么你需要將這兩個引用都設(shè)置為不可訪問。

在引用 DOM 樹中的內(nèi)部節(jié)點或葉節(jié)點時,還需要考慮另外一個問題。如果在代碼中保留對表單元格的引用(標(biāo)記),并決定從 DOM 中刪除表,同時保留對該特定單元格的引用,那么可能會出現(xiàn)內(nèi)存泄漏。

你可能認(rèn)為垃圾收集器將釋放除該單元格之外的所有內(nèi)容。然而,事實并非如此,由于單元格是表的一個子節(jié)點,而子節(jié)點保存對父節(jié)點的引用,所以對表單元格的這個引用將使整個表保持在內(nèi)存中,所以在移除有被引用的節(jié)點時候要移除其子節(jié)點。

編輯中可能存在的bug沒法實時知道,事后為了解決這些bug,花了大量的時間進行l(wèi)og 調(diào)試,這邊順便給大家推薦一個好用的BUG監(jiān)控工具Fundebug

原文:https://blog.sessionstack.com...

你的點贊是我持續(xù)分享好東西的動力,歡迎點贊!

交流

干貨系列文章匯總?cè)缦拢X得不錯點個Star,歡迎 加群 互相學(xué)習(xí)。

https://github.com/qq44924588...

我是小智,公眾號「大遷世界」作者,對前端技術(shù)保持學(xué)習(xí)愛好者。我會經(jīng)常分享自己所學(xué)所看的干貨,在進階的路上,共勉!

關(guān)注公眾號,后臺回復(fù)福利,即可看到福利,你懂的。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100081.html

相關(guān)文章

  • [譯]JavaScript工作內(nèi)存以及四種常見內(nèi)存泄漏

    摘要:是如何工作的內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏原文譯者幾個禮拜之前我們開始一系列對于以及其本質(zhì)工作原理的深入挖掘我們認(rèn)為通過了解的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應(yīng)用。 JavaScript是如何工作的:內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏 原文:How JavaScript works: memory management + how to han...

    tianren124 評論0 收藏0
  • 【譯】JavaScript工作內(nèi)存 + 4常見內(nèi)存泄露

    摘要:本文作為第三篇,將會討論另一個開發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會提供一些關(guān)于如何處理內(nèi)存泄露的技巧。這是當(dāng)前整型和雙精度的大小。然而,這是一組可以收集的內(nèi)存空間的近似值。 本文轉(zhuǎn)載自:眾成翻譯譯者:Leslie Wang審校: 為之漫筆鏈接:http://www.zcfy.cc/article/4211原文:https://blog.sessionstack.com/how-j...

    IntMain 評論0 收藏0
  • JavaScript內(nèi)存泄漏以及

    摘要:本文將會討論中的內(nèi)存泄漏以及如何處理,方便大家在使用編碼時,更好的應(yīng)對內(nèi)存泄漏帶來的問題。當(dāng)內(nèi)存不再需要時進行釋放大部分內(nèi)存泄漏問題都是在這個階段產(chǎn)生的,這個階段最難的問題就是確定何時不再需要已分配的內(nèi)存。中的相同對象稱為全局。 隨著現(xiàn)在的編程語言功能越來越成熟、復(fù)雜,內(nèi)存管理也容易被大家忽略。本文將會討論JavaScript中的內(nèi)存泄漏以及如何處理,方便大家在使用JavaScript...

    itvincent 評論0 收藏0
  • JavaScript 工作之三-內(nèi)存 4常見內(nèi)存泄漏問題(譯)

    摘要:這是因為我們訪問了數(shù)組中不存在的數(shù)組元素它超過了最后一個實際分配到內(nèi)存的數(shù)組元素字節(jié),并且有可能會讀取或者覆寫的位。包含個元素的新數(shù)組由和數(shù)組元素所組成中的內(nèi)存使用中使用分配的內(nèi)存主要指的是內(nèi)存讀寫。 原文請查閱這里,本文有進行刪減,文后增了些經(jīng)驗總結(jié)。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會討論日常使用中另一個被開發(fā)...

    weknow619 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<