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

資訊專欄INFORMATION COLUMN

JavaScript如何工作:內存管理+如何處理4個常見的內存泄漏

anRui / 1110人閱讀

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

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

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

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!

概述

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

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

即使在使用高級語言時,開發人員也應該了解內存管理(或者至少懂得一些基礎知識)。有時候,自動內存管理存在一些問題(例如垃圾收集器中的bug或實現限制等),開發人員必須理解這些問題,以便可以正確地處理它們(或者找到一個適當的解決方案,以最小代價來維護代碼)。

內存的生命周期

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

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

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

使用內存 — 這是程序實際使用之前分配的內存,在代碼中使用分配的變量時,就會發生讀和寫操作。

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

內存是什么?

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

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

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

很多東西都存儲在內存中:

程序使用的所有變量和其他數據。

程序的代碼,包括操作系統的代碼。

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

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

編譯器能夠立即知道所需的內存:4 + 4×4 + 8 = 28字節。

這段代碼展示了整型和雙精度浮點型變量所占內存的大小。但是大約20年前,整型變量通常占2個字節,而雙精度浮點型變量占4個字節。你的代碼不應該依賴于當前基本數據類型的大小。

編譯器將插入與操作系統交互的代碼,并申請存儲變量所需的堆棧字節數。

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

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

當函數調用其他函數時,每個函數在調用堆棧時獲得自己的塊。它保存所有的局部變量,但也會有一個程序計數器來記住它在執行過程中的位置。當函數完成時,它的內存塊將再次用于其他地方。

動態分配

不幸的是,當編譯時不知道一個變量需要多少內存時,事情就有點復雜了。假設我們想做如下的操作:

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

因此,它不能為堆棧上的變量分配空間。相反,我們的程序需要在運行時顯式地向操作系統請求適當的空間,這個內存是從堆空間分配的。靜態內存分配和動態內存分配的區別總結如下表所示:

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

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

在JavaScript中分配內存

現在將解釋第一步:如何在JavaScript中分配內存。

JavaScript為讓開發人員免于手動處理內存分配的責任——JavaScript自己進行內存分配同時聲明值。

某些函數調用也會導致對象的內存分配:

方法可以分配新的值或對象:

在JavaScript中使用內存

在JavaScript中使用分配的內存意味著在其中讀寫,這可以通過讀取或寫入變量或對象屬性的值,或者將參數傳遞給函數來實現。

當內存不再需要時進行釋放
大多數的內存管理問題都出現在這個階段

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

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

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

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

垃圾收集

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

內存引用

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

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

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

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

這是最簡單的垃圾收集算法。如果沒有指向對象的引用,則認為該對象是“垃圾可回收的”,如下代碼:

循環會產生問題

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

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

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

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

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

最后,垃圾收集器釋放所有未標記為活動的內存塊,并將該內存返回給操作系統。

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

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

在這篇文章中,你可以更詳細地閱讀到有關跟蹤垃圾收集的詳細信息,同時還包括了標記-清除算法及其優化。

循環不再是問題

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

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

垃圾收集器的反直觀行為

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

分配一組相當大的內在。

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

不再進一步的分配

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

內存泄漏是什么?

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

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

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

四種常見的內存泄漏 1.全局變量

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

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

等價于:

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

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

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

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

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

2.被遺忘的定時器和回調

setInterval為例,因為它在JavaScript中經常使用。

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

上面的代碼片段演示了使用定時器時引用不再需要的節點或數據。

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

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

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

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

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

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

3.閉包

閉包是javascript開發的一個關鍵方面,一個內部函數使用了外部(封閉)函數的變量。由于JavaScript運行的細節,它可能以下面的方式造成內存泄漏:

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

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

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

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

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

4.脫離DOM的引用

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

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

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

編輯中可能存在的bug沒法實時知道,事后為了解決這些bug,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具Fundebug

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

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

交流

干貨系列文章匯總如下,覺得不錯點個Star,歡迎 加群 互相學習。

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

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

關注公眾號,后臺回復福利,即可看到福利,你懂的。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100081.html

相關文章

  • [譯]JavaScript工作內存以及四種常見內存泄漏

    摘要:是如何工作的內存管理以及如何處理四種常見的內存泄漏原文譯者幾個禮拜之前我們開始一系列對于以及其本質工作原理的深入挖掘我們認為通過了解的構建方式以及它們是如何共同合作的,你就能夠寫出更好的代碼以及應用。 JavaScript是如何工作的:內存管理以及如何處理四種常見的內存泄漏 原文:How JavaScript works: memory management + how to han...

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

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

    IntMain 評論0 收藏0
  • JavaScript內存泄漏以及

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

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

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

    weknow619 評論0 收藏0

發表評論

0條評論

anRui

|高級講師

TA的文章

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