成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【前端進階之路】內(nèi)存基本知識

Simon_Zhou / 2737人閱讀

摘要:在運行腳本時,需要顯示的指定對象。大對象區(qū)每一個區(qū)域都是由一組內(nèi)存頁構成的。這里是唯一擁有執(zhí)行權限的內(nèi)存區(qū)。換句話說,是該對象被之后所能回收到內(nèi)存的總和。一旦活躍對象已被移出,則在舊的半空間中剩下的任何死亡對象被丟棄。

內(nèi)存管理

本文以V8為背景

對之前的文章進行重新編輯,內(nèi)容做了很多的調(diào)整,使其具有邏輯更加緊湊,內(nèi)容更加全面。

1. 基礎概念 1.1 生命周期

不管什么程序語言,內(nèi)存生命周期基本是一致的:

分配你所需要的內(nèi)存

使用分配到的內(nèi)存(讀、寫)

不需要時將其釋放、歸還

在所有語言中第一和第二部分都很清晰。最后一步在低級語言(例如C語言)中很清晰,但是在像JavaScript等高級語言中,這一步依賴于垃圾回收機制,一般情況下不用程序員操心。

1.2 堆與棧

我們知道,內(nèi)存空間可以分為??臻g和堆空間,其中

棧空間:由操作系統(tǒng)自動分配釋放,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結構中的棧。

堆空間:一般由程序員分配釋放,這部分空間就要考慮垃圾回收的問題。

1.3 基本類型與引用類型

在JavaScript中

基本類型:undefined,null,boolean,number,string,在內(nèi)存中占有固定的大小,他們的值保存在棧空間中,我們通過按值來訪問。

引用類型:Object,Array,Function,則在堆內(nèi)存中為這個值分配空間,然后把它的內(nèi)存地址保存在棧內(nèi)存中。(區(qū)分變量和對象)

1.4 V8的變量存放

handle

handle是指向?qū)ο蟮闹羔?,在V8中,所有對象都是通過handle來引用,handle主要用于V8的垃圾回收機制。進一步的,handle分為兩種:

持久化(Persistent handle),存放在堆上

本地化(Local handle),存放在棧上

scope

scope是handle的集合,可以包含若干個handle,這樣就無需將每個handle逐次釋放,而是直接釋放整個scope。

context

context是一個執(zhí)行器環(huán)境,使用context可以將相互分離的JavaScript腳本在同一個V8實例中運行,而不互相干涉。在運行JavaScript腳本時,需要顯示的指定context對象。

2. 垃圾回收 2.1 分代策略

腳本中,絕大多數(shù)對象的生存期很短,只有某些對象的生存期較長。為利用這一特點,V8將堆進行了分代。對象起初會被分配在新生區(qū)。在新生區(qū)的內(nèi)存分配非常容易:我們只需保有一個指向內(nèi)存區(qū)的指針,不斷根據(jù)新對象的大小對其進行遞增即可。當該指針達到了新生區(qū)的末尾,就會有一次清理(小周期),清理掉新生區(qū)中不活躍的死對象。對于活躍超過2個小周期的對象,則需將其移動至老生區(qū)。而在老生區(qū)則使用標記清除的算法來進行垃圾回收。V8通過分別對新生代對象和老生代對象使用不同的垃圾回收算法來提升來及回收的效率。這就是所謂的分代策略。

默認情況下,64位環(huán)境下的V8引擎的新生代內(nèi)存大小32MB、老生代內(nèi)存大小為1400MB,而32位則減半,分別為16MB和700MB

根據(jù)分代策略,V8將堆空間進行了分隔:

新生區(qū)

大多數(shù)對象被分配在這里,新生區(qū)是一個很小的區(qū)域,垃圾回收在這個區(qū)域非常頻繁,與其他區(qū)域相獨立。

老生指針區(qū)

這里包含大多數(shù)可能存儲指向其他對象的指針的對象,大多數(shù)在新生區(qū)存活了一段時間(2個周期)的對象都會被挪到這里。

老生數(shù)據(jù)區(qū)

這里存放只包含原始數(shù)據(jù)的對象,這些對象沒有執(zhí)行其他對象的指針,例如字符串,數(shù)字數(shù)組等,它們在新生區(qū)存活了一段時間后會被移動到這里。

大對象區(qū)

每一個區(qū)域都是由一組內(nèi)存頁構成的。除大對象區(qū)的內(nèi)存頁較大之外,每個區(qū)的內(nèi)存頁都是1MB大小,且按1MB內(nèi)存對齊。對象超過一定大小時就會被放置到這個區(qū),垃圾回收期從不移動這個區(qū)域的對象。

代碼區(qū)

代碼對象,也就是包含JIT之后指令的對象,會被分配到這里。這里是唯一擁有執(zhí)行權限的內(nèi)存區(qū)。(如果代碼對象因過大而被放到大對象區(qū),則該大對象所對應的內(nèi)存也是可執(zhí)行的。)

Cell區(qū)、屬性Cell區(qū)、Map區(qū)

這些區(qū)域存放Cell、屬性Cell和Map,每個區(qū)域因為都是存儲相同大小的元素,因此內(nèi)存結構很簡單,這里也是為了方便進行回收。

在 node-v4.x 之后,區(qū)域進行了合并為:新生區(qū),老生區(qū),大對象區(qū),Map區(qū),Code區(qū)

此外,對于一個對象所占的內(nèi)存空間,也涉及兩個概念:shallow sizeretained size。

shallow size就是對象本身占用內(nèi)存的大小,不包含其引用的對象。常規(guī)對象(非數(shù)組)的shallow size有其成員變量的數(shù)量和類型決定

retained size是該對象自己的shallow size,加上從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC之后所能回收到內(nèi)存的總和。

這兩個概念在使用chrome的開發(fā)工具中會看到。

垃圾回收釋放的內(nèi)存即為Retained Size的大小。

2.2 新生區(qū)的半空間分配策略

新生代使用半空間(Semi-space)分配策略,其中新對象最初分配在新生代的活躍半空間內(nèi)。一旦半空間已滿,一個Scavenge操作將活躍對象移出到其他半空間中,被認為是長期駐存的對象,并被晉升為老生代。一旦活躍對象已被移出,則在舊的半空間中剩下的任何死亡對象被丟棄。

具體的如下:

YG被平分為兩部分空間From和To,所有內(nèi)存從To空間被分配出去,當To滿時,開始觸發(fā)GC。

例如說:

某時刻,To已經(jīng)為A、B和C分配了內(nèi)存,當前它只剩下一小塊內(nèi)存未分配。而From所有的內(nèi)存都空閑著。

此時,一個程序需要為D分配內(nèi)存,但D需要的內(nèi)存大小超出了To未分配的內(nèi)存,此時觸發(fā)GC,頁面停止執(zhí)行

接著From和To進行對換,即原來的To空間被標志為From,F(xiàn)rom被標志為To。并且把活的變量值(B)標志出來,而垃圾(A、C)未被標志,它們將會被清掉。

活躍的變量(B)會被復制到To空間,而垃圾(A、C)則被回收。同時,D被分配到To空間,最后的情況如下。

至此,整個GC完成,此過程中頁面會阻塞,所以要盡可能的快。

2.2.1 對象的晉升

當一個新生代的對象在滿足一定條件下,會從新生代被移到老生代,這就是對象的晉升。具體的移動的標準有兩種

對象從From空間復制到To空間時,會檢查它的內(nèi)存地址來判斷這個對象是否經(jīng)歷過一次新生代的清理結果,如果是(說明存活了兩個周期了),則賦值到老生代中,否則則賦值到To空間中。

對象從From空間復制到To空間時,如果To空間已經(jīng)被使用了超過25%,那么這個對象直接被復制到老生代。

2.3 老生代

V8在老生代中采用Mark-Sweep和Mark-Compact相結合的垃圾回收策略。

2.3.1 標記

標記-清除算法分為標記和清除兩個階段。

標記階段,所有堆上的活躍對象都會被標記,每個內(nèi)存頁有一個用來標記對象的位圖,位圖中的每一位對應的內(nèi)存頁中的一個字,這個位圖需要占據(jù)一定的空間。另外還有兩位用來標記對象的狀態(tài):

如果一個對象為白對象,表示還未被垃圾回收器發(fā)現(xiàn)

如果一個對象為灰對象,表示已經(jīng)被垃圾回收器發(fā)現(xiàn),但其鄰接對象尚未處理

如果一個對象為黑對象,表示已經(jīng)被垃圾回收器發(fā)現(xiàn),其鄰接對象已全部處理

那么這里怎么理解標記的過程?這就必須知道:內(nèi)存管理方式實際上基于的概念。

GC Root是內(nèi)存的根節(jié)點,在瀏覽器中它是window,在Nodejs中則是global對象

圖的節(jié)點名稱是創(chuàng)建它的構造函數(shù)名

圖的邊是引用它的屬性名或者變量名

有很多內(nèi)部的GC Root對用戶來說都不是很重要,從應用的角度來說有下面幾種情況:

全局變量或者全局函數(shù)會一直被window這種全局對象所指向,它們會一直占據(jù)著內(nèi)存

DOM節(jié)點只有在被javascript對象引用的情況下,會留在內(nèi)存中。

在進行debug或者console的時候,可能會由于保留了上下文,導致本該被釋放的對象被保留下來。

實際上,標記的過程正是以由GC Root建立的圖為基礎,來實現(xiàn)對象的標記,標記算法的核心是深度優(yōu)先搜索,大致實現(xiàn)如下:

初始時,位圖為空,所有對象都是白對象。

從根對象(GC Root)到達的對象會被染為灰色,放到一個多帶帶的雙端隊列中。

標記階段,每次都會從雙端隊列中取出一個對象,并將其轉變?yōu)楹趯ο螅溧徑訉ο筠D變?yōu)榛?,然后把其鄰接對象加入到雙端隊列中。

如果雙端隊列為空或者所有對象都變成黑對象,則結束。

這個算法實現(xiàn)起來還是蠻繁瑣的,從的角度來看,其實標記的過程實際上是區(qū)分活節(jié)點和垃圾節(jié)點的過程。

從GC Root開始遍歷圖,所有能到達的節(jié)點稱為活節(jié)點。

GC Root不能到達的節(jié)點,該節(jié)點就成為垃圾,將會被回收。

標記結束后,所有的對象非黑(活躍節(jié)點)即白(垃圾節(jié)點)。

標記時間取決于必須標記的活躍對象的數(shù)目,對于一個大的web應用,整個堆棧的標記可能需要超過100ms。由于全停頓會造成了瀏覽器一段時間無響應,所以V8使用了一種增量標記的方式標記活躍對象,將完整的標記拆分成很多小的步驟,每做完一部分就停下來,讓JavaScript的應用線程執(zhí)行一會,這樣垃圾回收與應用線程交替執(zhí)行。V8可以讓每個標記步驟的持續(xù)時間低于5ms。

舉個例子:

window.ob = 2;
window.oa = {
    b1 : 3,
    b2 : {
        c1 : 4,
        c2 : "字符串"
    }
};
window.ob = undefined;

例如圖中灰色的節(jié)點,它原來代表ob變量值,當window.ob = undefined后,此節(jié)點與GC Root連接的路徑ob被切斷了,它就成了垃圾,將會被回收。

2.3.2 清除(Sweep)

由于標記完成后,所有對象都已經(jīng)被標記,即不是活躍對象就是死亡對象,堆上有多少空間已經(jīng)確定。清除時,垃圾回收器會掃描連續(xù)存放的死對象,將其變成空閑空間。這個任務是由專門的清掃線程同步執(zhí)行。

2.3.3 整理(Compact)

標記清除有一個問題就是進行一次標記清楚后,內(nèi)存空間往往是不連續(xù)的,會出現(xiàn)很多的內(nèi)存碎片。如果后續(xù)需要分配一個需要內(nèi)存空間較多的對象時,如果所有的內(nèi)存碎片都不夠用,將會使得V8無法完成這次分配,提前觸發(fā)垃圾回收。

標記整理正是為了解決標記清除所帶來的內(nèi)存碎片的問題。標記整理在標記清除的基礎進行修改,將其的清除階段變?yōu)榫o縮極端。在整理的過程中,將活著的對象向內(nèi)存區(qū)的一段移動,移動完成后直接清理掉邊界外的內(nèi)存。緊縮過程涉及對象的移動,所以效率并不是太好,但是能保證不會生成內(nèi)存碎片。

2.4 垃圾回收總結

新生代對象的Scavenge,這通常是快速的;

通過增量方式的標記步驟,依賴于需要標記的對象數(shù)量,時間可以任意長;

完整垃圾回收,這可能需要很長的時間;

帶內(nèi)存緊縮的完整垃圾回收,這也可能需要很長的時間,需要進行內(nèi)存緊縮。

3. 內(nèi)存問題 3.1 內(nèi)存泄漏

內(nèi)存泄漏是指計算機可用內(nèi)存的逐漸減少,原因通常是程序持續(xù)無法釋放其使用的臨時內(nèi)存。

先來一個最簡單的DOM泄漏的例子

var el = document.getElementById("_p");
el.mark = "marked";

//移除P
function removeP() {
    el.parentNode.removeChild(el);
    // el = null;
}

程序非常簡單,只是把id為_p的HTML元素從頁面移除,在移除之前從GC Root遍歷此P元素有兩條路可走。在執(zhí)行removeP()之后,按理來說該元素應該成為垃圾,所占有的內(nèi)存應該被釋放掉,但是由于還存在這路徑el沒有被切斷,p元素占有的內(nèi)存無法被釋放,導致了內(nèi)存泄漏。

3.2 內(nèi)存占用過多

這個問題很容易理解。例如使用事件代理來減少事件監(jiān)聽的函數(shù),從而減少內(nèi)存分配的開銷。

3.3 gc卡頓

如果你的頁面垃圾回收很頻繁,那說明你的頁面可能內(nèi)存使用分配太頻繁了。頻繁的GC可能也會導致頁面卡頓。

在一些框架中,如果創(chuàng)建一個大對象之后,可能不會很快就將其釋放,而是會緩存起來,直到?jīng)]有用處為止。

4. chrome dev tools

在使用Chrome進行內(nèi)存分析的時候,要先在chrome菜單-》工具,或者直接按shift+esc,找到內(nèi)存管理器,然后選上JavaScript使用的內(nèi)存(JavaScipt Memory)。

4.1 timeline

通過Timeline的內(nèi)存模式,可以在宏觀上觀察到web應用的內(nèi)存情況,一般我們需要關注的點:

GC的時間長度是否正常?

GC頻率是否正常?過于頻繁會導致卡頓

內(nèi)存趨勢圖是否正常?

DOM趨勢圖是否正常?

這些關注點都可以在timeline的內(nèi)存視圖中看到,如圖

timeline統(tǒng)計的內(nèi)存變化主要有:

js heap:堆空間

documents:文檔計數(shù)

node:dom節(jié)點數(shù)

event listener:事件監(jiān)聽器

CPU:在手機端暫時沒有

此外還可以通過event log看到這期間頁面執(zhí)行的操作

4.2 profile

profile面板我們關注的是Take Heap SnapshotRecode Heap Allocations

profile使用必須知道的:

標志為黃色的表示可能內(nèi)存泄漏

標志為紅色表示應該是發(fā)生內(nèi)存泄漏

在profile中的幾個概念:

(global property):全局對象,還有全局對象引用的對象

(closure):閉包,這里需要關注一下

(compiled code):V8會先代碼編譯成特定的語言,再執(zhí)行

(array,string,number,regexp):這些內(nèi)置對象的引用

HTML..Element:dom對象的引用

4.2.1 Take Heap Snapshot

使用快照,必須知道:

每次進行快照時,chrome都會先自動執(zhí)行一個gc

只有活躍的值,才會反映在快照里

快照有三個視圖,它們分別有各自的作用

Summary View

默認是以概要視圖顯示的,顯示了對象總數(shù),可以展開顯示具體內(nèi)容

Comparison View

該視圖用來對照不同的快照來找到快照之間的差異

Containment View

在這個視圖中,包括三個點

DOMWindow objects:js中的全局對象

GC Root:VM垃圾回收所使用的GC Root

Native Object:被放置到VM中的內(nèi)置對象

好吧。暫時不知道有什么用?以后再補充。

4.2.2 Recode Heap Allocations

這個功能可以動態(tài)監(jiān)控,通過次工具可以看到

什么時候分配了內(nèi)存,剛剛分配的內(nèi)存會以深藍色的柱子表示,柱子越高,內(nèi)存越大

什么時候回收了內(nèi)存,內(nèi)存被回收的時候,柱子變?yōu)榛疑?/p>

4.3 實踐

例子1:timeline來查看正常的內(nèi)存

例子2:通過timeline來發(fā)現(xiàn)內(nèi)存泄漏

可以看到隨著時間的增長,頁面占用的內(nèi)存越來越多,

在這種情況下就可以懷疑有內(nèi)存泄漏了,也有可能是瀏覽器還沒有進行gc,這個時候我們可以強制進行垃圾回收(垃圾筒圖標)

反復測試,如果發(fā)現(xiàn)無論怎么樣,內(nèi)存一直在增長,那么估計你就遇到內(nèi)存泄漏的問題了。

如果頁面中DOM節(jié)點的數(shù)量一直在攀升,那么肯定出現(xiàn)DOM泄漏了

例子3:驗證快照之前會進行gc

function Test (s) {
    this.s = s;
}
var _test1 = new Test("__________test___1_________");
var _test2 = new Test("__________test___2_________");
new Test("你看不到我,就是這么神奇");

例子4:通過snapshot來發(fā)現(xiàn)內(nèi)存泄漏

打開例子之后,先進行一次快照

點擊action,代表這用戶的交互

再進行一次快照

使用comparison視圖,對比兩次快照,如圖

可以看到,action之后,內(nèi)存的數(shù)量是增加的(注意,已經(jīng)gc過了),這說明web應用極有內(nèi)存泄漏。

一個原則就是找到本不應該存在卻還存在的那些值。

例子5:通過內(nèi)存分配的情況來分析

點擊藍色的柱子,可以看到詳細的情況,來進行分析

例子6:通過timeline來分析gc過于頻繁導致卡頓的問題

此例子在移動手機的瀏覽器進行測試,頁面還是相對簡單,在比較復雜的移動web應用,這種情況還是比較危險的,可能會導致頁面卡死。

參考

MDN:內(nèi)存管理

Chrome開發(fā)者工具之JavaScript內(nèi)存分析

Google V8的垃圾回收引擎

測試例子

如何編寫避免垃圾開銷的實時Javascript代碼

詳解js變量、作用域及內(nèi)存

V8 concept

淺談V8引擎中的垃圾回收機制

使用 Google V8 引擎開發(fā)可定制的應用程序

a tour of v8 garbage collection

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

轉載請注明本文地址:http://systransis.cn/yun/91632.html

相關文章

  • PHP 進階之路 - 億級 pv 網(wǎng)站架構實戰(zhàn)之性能壓榨

    摘要:業(yè)務和架構不分家,架構是建立在對業(yè)務的理解之上的。主鍵最好保持順序遞增,隨機主鍵會導致聚簇索引樹頻繁分裂,隨機增多,數(shù)據(jù)離散,性能下降。沒有索引的更新,可能會導致全表數(shù)據(jù)都被鎖住。 本博客并非全部原創(chuàng),其實是一個知識的歸納和匯總,里面我引用了很多網(wǎng)上、書上的內(nèi)容。也給出了相關的鏈接。 本文涉及的知識點比較多,大家可以根據(jù)關鍵字去搜索相關的內(nèi)容和購買相應的書籍進行系統(tǒng)的學習。不對的地方...

    SnaiLiu 評論0 收藏0
  • Java進階之路 - 收藏集 - 掘金

    摘要:請欣賞語法清單后端掘金語法清單翻譯自的,從屬于筆者的入門與實踐系列。這篇一篇框架整合友好的文章三后端掘金一理論它始終是圍繞數(shù)據(jù)模型頁面進行開發(fā)的。 RxJava 常用操作符 - Android - 掘金 原文地址 http://reactivex.io/documenta... ... RxJava 和 Retrofit 結合使用完成基本的登錄和注冊功能 - Android - 掘...

    BakerJ 評論0 收藏0
  • 說說我的web前端之路,分享些前端的好書

    摘要:推薦高性能網(wǎng)站建設指南高性能網(wǎng)站建設進階指南理由在讀完前幾本書之后我們對前端的性能和自己的代碼的效率已經(jīng)達到相當?shù)母叨攘耍缓笪覀冊诮佑|一些前端工程師的一些精髓。   WEB前端研發(fā)工程師,在國內(nèi)算是一個朝陽職業(yè),這個領域沒有學校的正規(guī)教育,大多數(shù)人都是靠自己自學成才。本文主要介紹自己從事web開發(fā)以來(從大二至今)看過的書籍和自己的成長過程,目的是給想了解JavaScript或者是剛...

    PascalXie 評論0 收藏0

發(fā)表評論

0條評論

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