【轉(zhuǎn)載】Chrome開發(fā)者工具之JavaScript內(nèi)存分析

內(nèi)存泄漏是指計算機可用內(nèi)存的逐漸減少躬络。當(dāng)程序持續(xù)無法釋放其使用的臨時內(nèi)存時就會發(fā)生。JavaScript的web應(yīng)用也會經(jīng)常遇到在原生應(yīng)用程序中出現(xiàn)的內(nèi)存相關(guān)的問題然磷,如泄漏和溢出匪燕,web應(yīng)用也需要應(yīng)對垃圾回收停頓

<article style="display: block;">盡管JavaScript使用垃圾回收進(jìn)行自動內(nèi)存管理榕订,但有效的(effective)內(nèi)存管理依然很重要。在這篇文章中我們將探討分析JavaScript web應(yīng)用中的內(nèi)存問題蜕便。在學(xué)習(xí)有關(guān)特性時請確保嘗試一下相關(guān)案例以提高你對這些工具在實踐中如何工作的認(rèn)識劫恒。請閱讀內(nèi)存 101(Memory 101)頁面來幫助你熟悉這篇文章中用到的術(shù)語。注意:我們將要用到的某些特性目前僅對Chrome Canary版瀏覽器可用轿腺。我們推薦使用這個版本來獲得最佳的工具两嘴,以分析你的應(yīng)用程序的內(nèi)存問題。

你需要思考的問題

總體來說族壳,當(dāng)你覺得你遇到了內(nèi)存泄漏問題時憔辫,你需要思考三個問題:

  • 我的頁面是否占用了過多的內(nèi)存? - Timeline內(nèi)存查看工具(Timeline memory view)Chrome任務(wù)管理(Chrome task manager) 能幫助你確認(rèn)你是否使用了過多的內(nèi)存。Memory view 能跟蹤頁面渲染過程中DOM節(jié)點計數(shù)仿荆,documents文檔計數(shù)和JS事件監(jiān)聽計數(shù)贰您。作為一個經(jīng)驗法則:避免對不再需要用到的DOM元素的引用坏平,移除不需要的事件監(jiān)聽并且在存儲你可能不會用到的大塊數(shù)據(jù)時要留意。
  • 我的頁面有沒有內(nèi)存泄漏? - 對象分配跟蹤(Object allocation tracker)通過實時查看JS對象的分配來幫助你定位泄漏锦亦。你也可以使用堆分析儀(Heap Profiler)生成JS堆快照舶替,通過分析內(nèi)存圖和比較快照之間的差異,來找出沒有被垃圾回收清理掉的對象杠园。
  • 我的頁面垃圾強制回收有多頻繁? - 如果你的頁面垃圾回收很頻繁顾瞪,那說明你的頁面可能內(nèi)存使用分配太頻繁了。Timeline內(nèi)存查看工具(Timeline memory view) 能夠幫助你發(fā)現(xiàn)感興趣的停頓抛蚁。
image

術(shù)語和基本概念

本小節(jié)介紹在內(nèi)存分析時使用的常用術(shù)語陈醒,這些術(shù)語在為其它語言做內(nèi)存分析的工具中也適用。這里的術(shù)語和概念用在了堆分析儀(Heap Profiler)UI工具和相關(guān)的文檔中瞧甩。

這些能夠幫助我們熟悉如何有效的使用內(nèi)存分析工具钉跷。如果你曾用過像Java、.NET等語言的內(nèi)存分析工具的話肚逸,那么這將是一個復(fù)習(xí)爷辙。

對象大小(Object sizes)

把內(nèi)存想象成一個包含基本類型(像數(shù)字和字符串)和對象(關(guān)聯(lián)數(shù)組)的圖表。它可能看起來像下面這幅一系列相關(guān)聯(lián)的點組成的圖吼虎。

image

一個對象有兩種使用內(nèi)存的方法:

  • 對象自身直接使用
  • 隱含的保持對其它對象的引用犬钢,這種方式會阻止垃圾回收(簡稱GC)對那些對象的自動回收處理。

當(dāng)你使用DevTools中的堆分析儀(Heap Profiler思灰,用來分析內(nèi)存問題的工具玷犹,在DevTools的”Profile”標(biāo)簽下)時,你可能會驚喜的發(fā)現(xiàn)一些顯示各種信息的欄目洒疚。其中有兩項是:直接占用內(nèi)存(Shallow Size)占用總內(nèi)存(Retained Size)歹颓,那它們是什么意思呢?

image

直接占用內(nèi)存(Shallow Size油湖,不包括引用的對象占用的內(nèi)存)

這個是對象本身占用的內(nèi)存巍扛。

典型的JavaScript對象都會有保留內(nèi)存用來描述這個對象和存儲它的直接值。一般乏德,只有數(shù)組和字符串會有明顯的直接占用內(nèi)存(Shallow Size)撤奸。但字符串和數(shù)組常常會在渲染器內(nèi)存中存儲主要數(shù)據(jù)部分,僅僅在JavaScript對象棧中暴露一個很小的包裝對象喊括。

渲染器內(nèi)存指你分析的頁面在渲染的過程中所用到的所有內(nèi)存:頁面本身的內(nèi)存 + 頁面中的JS堆用到的內(nèi)存 + 頁面觸發(fā)的相關(guān)工作進(jìn)程(workers)中的JS堆用到的內(nèi)存胧瓜。然而,通過阻止垃圾自動回收別的對象郑什,一個小對象都有可能間接占用大量的內(nèi)存府喳。

占用總內(nèi)存(Retained Size,包括引用的對象所占用的內(nèi)存)

一個對象一但刪除后它引用的依賴對象就不能被GC根(GC root)引用到蘑拯,它們所占用的內(nèi)存就會被釋放钝满,一個對象占用總內(nèi)存包括這些依賴對象所占用的內(nèi)存兜粘。

GC根是由控制器(handles)組成的,這些控制器(不論是局部還是全局)是在建立由build-in函數(shù)(native code)到V8引擎之外的JavaScript對象的引用時創(chuàng)建的弯蚜。所有這些控制器都能夠在堆快照的GC roots(GC根) > Handle scopeGC roots >Global handlers中找到孔轴。如果不深入了解瀏覽器的實現(xiàn)原理,在這篇文章中介紹這些控制器可能會讓人不能理解熟吏。GC根和控制器你都不需要過多關(guān)心距糖。

有很多內(nèi)部的GC根對用戶來說都是不重要的玄窝。從應(yīng)用的角度來說有下面幾種情況:

  • Window 全局對象 (所有iframe中的)牵寺。在堆快照中有一個distance字段,它是從window對象到達(dá)對應(yīng)對象的最短路徑長度恩脂。
  • 由所有document能夠遍歷到的DOM節(jié)點組成的文檔DOM樹帽氓。不是所有節(jié)點都會被對應(yīng)的JS引用挡育,但有JS引用的節(jié)點在document存在的情況下都會被保留肤寝。
  • 有很多對象可能是在調(diào)試代碼時或者DevTools console中(比如:console中的一些代碼執(zhí)行結(jié)束后)創(chuàng)建出來的裆操。

注意:我們推薦用戶在創(chuàng)建堆快照時韭畸,不要在console中執(zhí)行代碼臂港,也不要啟用調(diào)試斷點仲智。

內(nèi)存圖由一個根部開始娘汞,可能是瀏覽器的window對象或Node.js模塊Global對象烈钞。這些對象如何被內(nèi)存回收不受用戶的控制漫仆。

image

不能被GC根遍歷到的對象都將被內(nèi)存回收捎拯。

注意:直接占用內(nèi)存和占用總內(nèi)存字段中的數(shù)據(jù)是用字節(jié)表示的。

對象的占用總內(nèi)存樹

之前我們已經(jīng)了解到盲厌,堆是由各種互相關(guān)聯(lián)的對象組成的網(wǎng)狀結(jié)構(gòu)署照。在數(shù)字領(lǐng)域,這種結(jié)構(gòu)被稱為或內(nèi)存圖吗浩。圖是由邊緣(edges)連接著的節(jié)點(nodes)組成的建芙,他們都被貼了標(biāo)簽。

  • 節(jié)點(Nodes) (或?qū)ο?/em>) 節(jié)點的標(biāo)簽名是由創(chuàng)建他們的構(gòu)造(constructor)函數(shù)的名稱確定
  • 邊緣(Edges) 標(biāo)簽名就是屬性名

本文檔的后面你將了解到如何使用堆分析儀生成快照懂扼。從下圖的堆分析儀生成的快照中禁荸,我們能看到距離(distance)這個字段:是指對象到GC根的距離。如果同一個類型的所有對象的距離都一樣阀湿,而有一小部分的距離卻比較大赶熟,那么就可能出了些你需要進(jìn)行調(diào)查的問題了。

image

支配對象(Dominators)

支配對象就像一個樹結(jié)構(gòu)炕倘,因為每個對象都有一個支配者钧大。一個對象的支配者可能不會直接引用它支配的對象,就是說罩旋,支配對象樹結(jié)構(gòu)不是圖中的生成樹啊央。

image

在上圖中:

  • 節(jié)點1支配節(jié)點2
  • 節(jié)點2支配節(jié)點3眶诈,4和6
  • 節(jié)點3支配節(jié)點5
  • 節(jié)點5支配節(jié)點8
  • 節(jié)點6支配節(jié)點7

在下圖的例子中,節(jié)點#3#10的支配者瓜饥,但#7也在每個從GC到#10的路經(jīng)中都出現(xiàn)了逝撬。像這樣,如果B對象在每個從根節(jié)點到A對象的路經(jīng)中都出現(xiàn)乓土,那么B對象就是A對象的支配對象宪潮。

image

V8介紹

在本節(jié),我們將描述一些內(nèi)存相關(guān)的概念趣苏,這些概念是和V8 JavaScript虛擬機(V8 VM 或VM)有關(guān)的狡相。當(dāng)分析內(nèi)存時,了解這些概念對理解堆快照是有幫助的食磕。

JavaScript對象描述

有三個原始類型:

  • 數(shù)字(Numbers) (如 3.14159..)
  • 布爾值(Booleans) (true或false)
  • 字符型(Strings) (如 ‘Werner Heisenberg’)

它們不會引用別的值尽棕,它們只會是葉子節(jié)點或終止節(jié)點。

數(shù)字(Numbers)以下面兩種方式之一被存儲:

  • 31位整數(shù)直接值彬伦,稱做:小整數(shù)(small integers)(SMIs)滔悉,或
  • 堆對象,引用為堆值单绑。堆值是用來存儲不適合用SMI形式存儲的數(shù)據(jù)回官,像雙精度數(shù)(doubles),或者當(dāng)一個值需要被打包(boxed)時搂橙,如給這個值再設(shè)置屬性值歉提。

字符型數(shù)據(jù)會以下面兩種方式存儲:

  • VM堆,或
  • 外部的渲染器內(nèi)存中份氧。這時會創(chuàng)建一個包裝對象用來訪問存儲的位置唯袄,比如,Web頁面包存的腳本資源和其它內(nèi)容蜗帜,而不是直接復(fù)制至VM堆中恋拷。

新創(chuàng)建的JavaScript對象會被在JavaScript堆上(或VM堆)分配內(nèi)存。這些對象由V8的垃圾回收器管理厅缺,只要還有一個強引用他們就會在內(nèi)存中保留蔬顾。

本地對象是所有不在JavaScript堆中的對象,與堆對象不同的是湘捎,在它們的生命周期中诀豁,不會被V8垃圾加收器處理,只能通過JavaScript包裝對象引用窥妇。

連接字符串是由一對字符串合并成的對象舷胜,是合并后的結(jié)果。連接字符串只在有需要時合并活翩。像一連接字符串的子字符串需要被構(gòu)建時烹骨。

比如:如果你連接ab翻伺,你得到字符串(a, b)這用來表示連接的結(jié)果。如果你之后要再把這個結(jié)果與d連接沮焕,你就得到了另一個連接字符串((a, b), d)吨岭。

數(shù)組(Arrays) - 數(shù)組是數(shù)字類型鍵的對象。它們在V8引擎中存儲大數(shù)據(jù)量的數(shù)據(jù)時被廣泛的使用峦树。像字典這種有鍵-值對的對象就是用數(shù)組實現(xiàn)的辣辫。

一個典型的JavaScript對象可以通過兩種數(shù)組類型之一的方式來存儲:

  • 命名屬性,和
  • 數(shù)字化的元素

如果只有少量的屬性魁巩,它們會被直接存儲在JavaScript對象本身中急灭。

Map - 一種用來描述對象類型和它的結(jié)構(gòu)的對象。比如歪赢,maps會被用來描述對象的結(jié)構(gòu)以實現(xiàn)對對象屬性的快速訪問

對象組

每個本地對象組都是由一組之間相互關(guān)聯(lián)的對象組成的化戳。比如一個DOM子樹单料,每個節(jié)點都能訪問到它的父元素埋凯,下一個子元素和下一個兄弟元素,它們構(gòu)成了一個關(guān)聯(lián)圖扫尖。需要注意的是本地元素沒有在JavaScript堆中表現(xiàn)-這就是它們的大小是零的原因白对,而它的包裝對象被創(chuàng)建了。

每個包裝對象都會有一個到本地對象的引用换怖,用來傳遞對這些本地對象的操作甩恼。這些本地對象也有到包裝對象的引用。但這并不會創(chuàng)造無法收回的循環(huán)沉颂,GC是足夠智能的条摸,能夠分辨出那些已經(jīng)沒有引用包裝對象的本地對象并釋放它們的。但如果有一個包裝對象沒有被釋放那它將會保留所有對象組和相關(guān)的包裝對象铸屉。

先決條件和有用提示

Chrome 任務(wù)管理器

注意: 當(dāng)使用Chrome做內(nèi)存分析時钉蒲,最好設(shè)置一個潔凈的測試環(huán)境

打開Chrome的內(nèi)存管理器,觀察內(nèi)存字段彻坛,在一個頁面上做相關(guān)的操作顷啼,你可以很快定位這個操作是否會導(dǎo)致頁面占用很多內(nèi)存。你可以從Chrome菜單 > 工具或按Shift + Esc昌屉,找到內(nèi)存管理器钙蒙。

image

打開后,在標(biāo)頭右擊選用 JavasScript使用的內(nèi)存 這項间驮。

通過DevTools Timeline來定位內(nèi)存問題

解決問題的第一步就是要能夠證明問題存在躬厌。這就需要創(chuàng)建一個可重現(xiàn)的測試來做為問題的基準(zhǔn)度量。沒有可再現(xiàn)的程序竞帽,就不能可靠的度量問題扛施。換句話說如果沒有基準(zhǔn)來做為對比偏陪,就無法知道是哪些改變使問題出現(xiàn)的。

時間軸面版(Timeline panel)對于發(fā)現(xiàn)程序什么時候出了問題很用幫助煮嫌。它展示了你的web應(yīng)用或網(wǎng)站加載和交互的時刻笛谦。所有的事件:從加載資源到解JavaScript,樣式計算昌阿,垃圾回收停頓和頁面重繪饥脑。都在時間軸上表示出來了。

當(dāng)分析內(nèi)存問題時懦冰,時間軸面版上的內(nèi)存視圖(Memory view)能用來觀察:

  • 使用的總內(nèi)存 – 內(nèi)存使用增長了么?
  • DOM節(jié)點數(shù)
  • 文檔(documents)數(shù)
  • 注冊的事件監(jiān)聽器(event listeners)數(shù)
image

更多的關(guān)于在內(nèi)存分析時灶轰,定位內(nèi)存泄漏的方法,請閱Zack Grossbart的Memory profiling with the Chrome DevTools

證明一個問題的存在

首先要做的事情是找出你認(rèn)為可能導(dǎo)致內(nèi)存泄漏的一些動作刷钢∷癫可以是發(fā)生在頁面上的任何事件,鼠標(biāo)移入内地,點擊伴澄,或其它可能會導(dǎo)致頁面性能下降的交互。

在時間軸面版上開始記錄(Ctrl+E 或 Cmd+E)然后做你想要測試的動作阱缓。想要強制進(jìn)行垃圾回收點面版上的垃圾筒圖標(biāo)([圖片上傳失敗...(image-459677-1569383413904)] )非凌。

下面是一個內(nèi)存泄漏的例子,有些點沒有被垃圾回收:

image

如果經(jīng)過一些反復(fù)測試后荆针,你看到的是鋸齒狀的圖形(在內(nèi)存面版的上方)敞嗡,說明你的程序中有很多短時存在的對象。而如果一系列的動作沒有讓內(nèi)存保持在一定的范圍航背,并且DOM節(jié)點數(shù)沒有返回到開始時的數(shù)目喉悴,你就可以懷疑有內(nèi)存泄漏了。

image

一旦確定了存在內(nèi)存上的問題玖媚,你就可以使用分析面板(Profiles panel)上的堆分析儀(heap profiler)來定位問題的來源箕肃。

例子: 嘗試一下memory growth的例子,能幫助你有效的練習(xí)通過時間軸分析內(nèi)存問題最盅。

內(nèi)存回收

內(nèi)存回收器(像V8中的)需要能夠定位哪些對象是活的(live)突雪,而那些被認(rèn)為是死的(垃圾)的對象是無法引用到的(unreachable)

如果垃圾回收 (GC)因為JavaScript執(zhí)行時有邏輯錯誤而沒有能夠回收到垃圾對象涡贱,這些垃圾對象就無法再被重新回收了咏删。像這樣的情況最終會讓你的應(yīng)用越來越慢。

比如你在寫代碼時问词,有的變量和事件監(jiān)聽器已經(jīng)用不到了督函,但是卻仍然被有些代碼引用。只要引用還存在,那被引用的對象就無法被GC正確的回收辰狡。

當(dāng)你的應(yīng)用程序在運行中锋叨,有些DOM對象可能已經(jīng)更新/移除了,要記住檢查引用了DOM對象的變量并將其設(shè)null宛篇。檢查可能會引用到其它對象(或其它DOM元素)的對象屬性娃磺。雙眼要盯著可能會越來越增長的變量緩存。

堆分析儀

拍一個快照

在Profiles面板中叫倍,選擇Take Heap Snapshot偷卧,然后點擊Start或者按Cmd + E或者Ctrl + E:

image

快照最初是保存在渲染器進(jìn)程內(nèi)存中的。它們被按需導(dǎo)入到了DevTools中吆倦,當(dāng)你點擊快照按鈕后就可以看到它們了听诸。當(dāng)快照被載入DevTools中顯示后,快照標(biāo)題下面的數(shù)字顯示了能夠被引用到的(reachable)JavaScript對象占有內(nèi)存總數(shù)蚕泽。

image

例子:嘗試一下garbage collection in action的例子晌梨,在時間軸(Timeline)面板中監(jiān)控內(nèi)存的使用。

清除快照

點擊Clear all按鈕圖標(biāo)([圖片上傳失敗...(image-dbe8df-1569383413904)] )须妻,就能清除掉所有快照:

image

注意:關(guān)閉DevTools窗口并不能從渲染內(nèi)存中刪除掉收集的快照仔蝌。當(dāng)重新打開DevTools后,之前的快照列表還在璧南。

記住我們之前提到的掌逛,當(dāng)你生成快照時你可以強制執(zhí)行在DevTools中GC。當(dāng)我們拍快照時司倚,GC是自動執(zhí)行的。在時間軸(Timeline)中點擊垃圾桶(垃圾回收)按鈕([圖片上傳失敗...(image-2ed01-1569383413904)] )就可以輕松的執(zhí)行垃圾回收了篓像。

image

例子:嘗試一下scattered objects并用堆分析儀(Heap Profiler)分析它动知。你可以看到(對象)項目的集合。

切換快照視圖

一個快照可以根據(jù)不同的任務(wù)切換視圖员辩『辛福可以通過如圖的選擇框切換:

image

下面是三個默認(rèn)視圖:

  • Summary(概要) - 通過構(gòu)造函數(shù)名分類顯示對象;
  • Comparison(對照) - 顯示兩個快照間對象的差異奠滑;
  • Containment(控制) - 可用來探測堆內(nèi)容丹皱;

Dominators(支配者)視圖可以在Settings面板中開啟 – 顯示dominators tree. 可以用來找到內(nèi)存增長點。

通過不同顏色區(qū)分對象

對象的屬性和屬性值有不同的類型并自動的通過顏么進(jìn)行了區(qū)分宋税。每個屬性都是以下四種之一:

  • a:property - 通過名稱索引的普通屬性摊崭,由.(點)操作符,或引用杰赛,如["foo bar"]呢簸;
  • 0:element - 通過數(shù)字索引的普通屬性,由引用;
  • a:context var - 函數(shù)內(nèi)的屬性根时,在函數(shù)上下文內(nèi)瘦赫,通過名稱引用;
  • a:system prop - 由JavaScript VM 添加的屬性蛤迎,JavaScript代碼不能訪問确虱。

命名為System的對象沒有對應(yīng)的JavaScript類型。它們是JavaScript VM對象系統(tǒng)內(nèi)置的替裆。V8將大多數(shù)內(nèi)置對象和用戶JS對象放在同一個堆中蝉娜。但它們只是V8的內(nèi)部對象。

視圖詳解

Summary view(概要視圖)

打開一個快照扎唾,默認(rèn)是以概要視圖顯示的召川,顯示了對象總數(shù),可以展開顯示具體內(nèi)容: Initially, a snapshot opens in the Summary view, displaying object totals, which can be expanded to show instances:

image

第一層級是”總體”行胸遇,它們顯示了:

  • Constructor(構(gòu)造函數(shù))表示所有通過該構(gòu)造函數(shù)生成的對象
  • 對象的實例數(shù)在Objects Count列上顯示
  • Shallow size列顯示了由對應(yīng)構(gòu)造函數(shù)生成的對象的shallow sizes(直接占用內(nèi)存)總數(shù)
  • Retained size列展示了對應(yīng)對象所占用的最大內(nèi)存
  • Distance列顯示的是對象到達(dá)GC根的最短距離

展開一個總體行后荧呐,會顯示所有的對象實例。沒一個實例的直接占用內(nèi)存和占用總內(nèi)存都被相應(yīng)顯示纸镊。@符號后的數(shù)字不對象的唯一ID倍阐,有了它你就可以逐個對象的在不同快照間作對比。

例子:嘗試這個例子(在新tab標(biāo)簽中打開)來了解如何使用概要視圖逗威。

記住黃色的對象被JavaScript引用峰搪,而紅色的對象是由黃色背景色引用被分離了的節(jié)點。

Comparison view(對照視圖)

該視圖用來對照不同的快照來找到快照之間的差異凯旭,來發(fā)現(xiàn)有內(nèi)存泄漏的對象概耻。來證明對應(yīng)用的某個操作沒有造成泄漏(比如:一般一對操作和撤消的動作,像找開一個document罐呼,然后關(guān)閉鞠柄,這樣是不會造成泄漏的),你可以按以下的步驟嘗試:

  1. 在操作前拍一個堆快照嫉柴;
  2. 執(zhí)行一個操作(做你認(rèn)為會造成泄漏的動作)厌杜;
  3. 撤消之前的操作(上一個操作相反的操作,多重復(fù)幾次)计螺;
  4. 拍第二個快照夯尽,將視圖切換成對照視圖,并同快照1進(jìn)行對比登馒。

在對照視圖下匙握,兩個快照之間的不同就會展現(xiàn)出來了。當(dāng)展開一個總類目后谊娇,增加和刪除了的對象就顯示出來了:

image

例子:嘗試例子(在新tab標(biāo)簽中打開)來了解如何使用對照視圖來定位內(nèi)存泄漏肺孤。

Containment view(控制視圖)

控制視圖可以稱作對你的應(yīng)用的對象結(jié)構(gòu)的”鳥瞰視圖(bird’s eys view)”罗晕。它能讓你查看function內(nèi)部,跟你的JavaScript對象一樣的觀察VM內(nèi)部對象赠堵,能讓你在你的應(yīng)用的非常低層的內(nèi)存使用情況小渊。

該視圖提供了幾個進(jìn)入點:

  • DOMWindow 對象 - 這些對象是JavaScript代碼的”全局”對象;
  • GC根 - VM的垃圾回收器真正的GC根茫叭;
  • Native對象 - 瀏覽器對象對”推入”JavaScript虛擬機中來進(jìn)行自動操作酬屉,如:DOM節(jié)點,CSS規(guī)則(下一節(jié)會有詳細(xì)介紹揍愁。)

下圖是一個典型的控制視圖:

image

例子:嘗試例子(在新tab標(biāo)簽中打開)來了解如何使用控制視圖來查看閉包內(nèi)部和事件處理呐萨。

關(guān)于閉包的建議

給函數(shù)命名對你在快照中的閉包函數(shù)間作出區(qū)分會很用幫助。如:下面的例子中沒有給函數(shù)命名:

</article>

<pre class="brush: actionscript3; gutter: true; first-line: 1 hljs php" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; overflow-wrap: break-word; white-space: pre-wrap; font: 12px/20px "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial;">function createLargeClosure() {
var largeStr = new Array(1000000).join('x');

var lC = function() { // this is NOT a named function
return largeStr;
};

return lC;
}</pre>

<article style="display: block;">而下面這個有給函數(shù)命名:</article>

<pre class="brush: actionscript3; gutter: true; first-line: 1 hljs php" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; overflow-wrap: break-word; white-space: pre-wrap; font: 12px/20px "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial;">function createLargeClosure() {
var largeStr = new Array(1000000).join('x');

var lC = function lC() { // this IS a named function
return largeStr;
};

return lC;
}</pre>

image

例子:嘗試這個例子why eval is evil來分析內(nèi)存中閉包的影響莽囤。你可能也對嘗試下面這個例子谬擦,記錄heap allocations(堆分配)有興趣。

揭露DOM內(nèi)存泄漏

這個工具獨一無二的一點是展示了瀏覽器原生對象(DOM節(jié)點朽缎,CSS規(guī)則)和JavaScript對象之間的雙向引用惨远。這能幫助你發(fā)現(xiàn)因為忘記解除引用游離的DOM子節(jié)點而導(dǎo)致的難以發(fā)覺的內(nèi)存泄漏。

DOM內(nèi)存泄漏可能會超出你的想象话肖”被啵看下下面的例子 – #tree對象什么時候被GC呢?

<pre class="brush: actionscript3; gutter: true; first-line: 1 hljs cs" style="margin: 15px auto; padding: 10px 15px; display: block; overflow-x: auto; color: rgb(51, 51, 51); background: rgb(251, 251, 251); word-break: break-all; overflow-wrap: break-word; white-space: pre-wrap; font: 12px/20px "courier new"; border-width: 1px 1px 1px 4px; border-style: solid; border-color: rgb(221, 221, 221); border-image: initial;">var select = document.querySelector;
var treeRef = select("#tree");
var leafRef = select("#leaf");
var body = select("body");

body.removeChild(treeRef);

//#tree can't be GC yet due to treeRef
treeRef = null;

//#tree can't be GC yet due to indirect
//reference from leafRef

leafRef = null;
//#NOW can be #tree GC</pre>

#leaf代表了對它的父節(jié)點的引用(parentNode)它遞歸引用到了#tree最筒,所以贺氓,只有當(dāng)leafRef被nullified后#tree代表的整個樹結(jié)構(gòu)才會被GC回收。

image

例子:嘗試leaking DOM nodes來了解哪里DOM節(jié)點會內(nèi)存泄漏并如何定位床蜘。你也可以看一下這個例子:DOM leaks being bigger than expected辙培。

查看Gonzalo Ruiz de Villa的文章Finding and debugging memory leaks with the Chrome DevTools來閱讀更多關(guān)于DOM內(nèi)存泄漏和內(nèi)存分析的基礎(chǔ)。

原生對象在Summary和Containment視呼中更容易找到 – 有它們專門的類目:

image

例子:嘗試下這個例子(在新tab標(biāo)簽中打開)來了解如何將DOM樹分離悄泥。

支配者視圖(Dominators view)

支配者視圖顯示了堆圖的支配者樹虏冻。支配者視圖跟控制(Containment)視圖很像,但是沒有屬性名弹囚。這是因為支配者可能會是一個沒有直接引用的對象,就是說這個支配者樹不是堆圖的生成樹领曼。但這是個有用的視圖能幫助我們很快的定位內(nèi)存增長點鸥鹉。

注意:在Chrome Canary中,支配者視圖能夠在DevTools中的Settings > Show advanced heap snapshot properties 開啟庶骄,重啟DevTools生效毁渗。

image

例子:嘗試這個例子(在新tab標(biāo)簽中打開)來練習(xí)如何找到內(nèi)存增長點〉サ螅可以進(jìn)一步嘗試下一個例子retaining paths and dominators灸异。

對象分配跟蹤器

對象跟蹤器整合了heap profiler的快照增量更新分析和Timeline面板的記錄。跟其它工具一樣,記錄對象的堆配置需要啟動記錄肺樟,執(zhí)行一系列操作檐春,然后停止記錄然后進(jìn)行分析。

對象跟蹤器不間斷的記錄堆快照(頻率達(dá)到了每50毫秒么伯!)疟暖,結(jié)束時記錄最后一個快照。該堆分配分析器顯示對象在哪被創(chuàng)建并定位它的保留路徑田柔。

image

開啟并使用對象分析器

開始使用對象分析器: 1. 確認(rèn)你使用的是最新版的Chrome Canary俐巴。

  1. 打開DeveTools并點擊齒輪圖標(biāo)(譯者:沒明白這步有什么用)。
  2. 現(xiàn)在硬爆,打開Profiler面板欣舵,你就能看到”Record Heap Allocations”的選項。
image

上面的柱條表示在堆中生成的新對象缀磕。高度就對應(yīng)了相應(yīng)對象的大小缘圈,它的顏色表示了這個對象是否在最后拍的那個快照中還在:藍(lán)色柱表示在timeline最后這個對象還在,灰色柱表示這個對象在timeline中生成虐骑,但結(jié)束前已經(jīng)被內(nèi)存回收了准验。

image

上面的例子中,一個動作執(zhí)行了10次廷没。同一個程序保留了5個對象糊饱,所以最后5個藍(lán)色柱條被保留了。但這最后留下的柱存在潛在的問題颠黎。你可以用timeline上的滑動條縮小到那個特定的快照并找到這個分配的對象另锋。

image

點擊一個堆中的對象就能在堆快照的下面部分顯示它的保留總內(nèi)存樹。檢查這個對象的保留總內(nèi)存樹能夠給你足夠的信息來了解為什么這個對象沒有被回收狭归,然后你就能對代碼做相應(yīng)的修改來去掉不必要的引用夭坪。

內(nèi)存分析FAQ

問:我不能看到對象的所有屬性,我也看到它們的非字符串值过椎!為什么室梅?

并非所有屬性都完整的保存在JavaScript堆中。其中有些是通過執(zhí)行原生代碼的getters方法來獲取的疚宇。這些屬性沒有在堆快照中捕獲亡鼠,是為了防止對getters方法的調(diào)用和避免程序狀態(tài)的改變,如果這些getters方法不是”純(Pure)”的functions敷待。同樣间涵,非字符串的值,如數(shù)字榜揖,沒有被捕獲是為了減少快照的大小勾哩。

問:@符號后面的數(shù)字是什么意思 – 是地址還是ID呢抗蠢?這個ID值真的是唯一的么?

這是對象ID思劳。顯示對象的地址沒有意義迅矛,因為一個對象會在垃圾回收的時候被移除。這些對象IDs是真正的IDs – 就是說敢艰,它們在不同的快照間是唯一表示的诬乞。這樣就可以的堆狀態(tài)間進(jìn)行精確的對比。維持這些IDs會給GC流程增加額外的開支钠导,但這僅在記錄第一次堆快照時分配 – 如果堆分析儀沒有用到震嫉,就不會有額外的開支。

問:”死”(無法引用到的)對象被包含在快照中了么牡属?

沒有票堵,只有可以引用到的對象才會顯示在快照中。而且逮栅,拍快照前都會先自動執(zhí)行GC操作悴势。

注意:在寫這篇文章的時候,我們計劃在拍快照的時候不再GC措伐,防止堆尺寸的減少√叵耍現(xiàn)在已經(jīng)是這樣了,但垃圾對象依然顯示在快照之外侥加。

問:GC根是由什么組成的捧存?

由很多部分組成:

  • 原生對象圖;
  • 符號表担败;
  • VM線程中的棧昔穴;
  • 編輯緩存;
  • 控制器上下文提前;
  • 全局控制器吗货。
image

問:我得知可以使用Heap Profiler和Timeline Memory view來檢測內(nèi)存泄漏。但我應(yīng)該先用哪個工具呢狈网?

Timeline面版宙搬,是在你第一次使用你的頁面發(fā)現(xiàn)速度變慢了時用來論斷過多的內(nèi)存使用。網(wǎng)站變慢是比較典型的內(nèi)存泄漏的信號拓哺,但也可能是其它的原因 – 可能是有渲染或網(wǎng)絡(luò)傳輸方面的瓶頸害淤,所以要確保解決你網(wǎng)頁的真正問題。

論斷是否是內(nèi)存問題拓售,就打開Timeline面板和Memory標(biāo)簽。點擊record按鈕镶奉,然后在你的應(yīng)用上重復(fù)幾次你認(rèn)為可能導(dǎo)致內(nèi)存泄漏的操作础淤。停止記錄崭放。你應(yīng)用的內(nèi)存使用圖就生成出來了。如果內(nèi)存的使用一直在增長(而沒有相應(yīng)的下降)鸽凶,這就表明你的應(yīng)用可能有內(nèi)存泄漏了币砂。

一般一個正常的應(yīng)用的內(nèi)存使用圖形是鋸齒狀的,因為內(nèi)存使用后又會被垃圾回收器回收玻侥。不用擔(dān)心這種鋸齒形 – 因為總是會因為JavaScript而有內(nèi)存的消耗决摧,甚至一個空的requestAnimationFrame也會造成這種鋸齒形,這是無法避免的凑兰。只要不是那種分配了持續(xù)很多內(nèi)存的形狀掌桩,那就表明生成了很多內(nèi)存垃圾。

image

上圖的增長線是需要你警惕的姑食。在診斷分析的時候Memory標(biāo)簽中的DOM node counter波岛,Document counter和Event listener count也是很有用的。DOM節(jié)點數(shù)是使用的原生內(nèi)存不會影響JavaScript內(nèi)存圖音半。

image

一旦你確認(rèn)你的應(yīng)用有內(nèi)存泄漏则拷,堆分析儀就可以用來找到內(nèi)存泄漏的地方。

問:我發(fā)現(xiàn)堆快照中有的DOM節(jié)點的數(shù)字是用紅色標(biāo)記為”Detached DOM tree”曹鸠,而其它的是黃色的煌茬,這是什么意思呢?

你會發(fā)現(xiàn)有不同的顏色彻桃。紅色的節(jié)點(有著深色的背景)沒有從JavaScript到它們的直接的引用坛善,但它們是分離出來的DOM結(jié)構(gòu)的一部分,所以他們還是在內(nèi)存中保留了叛薯。有可能有一個節(jié)點被JavaScript引用到了(可能是在閉包中或者一個變量)浑吟,這個引用會阻止整個DOM樹被內(nèi)存回收。

image

黃色節(jié)點(黃色背景)有JavaScript的直接引用耗溜。在同一個分離的DOM樹中查看一個黃色的節(jié)點來定位你的JavaScript的引用组力。就可能看到從DOM window到那個節(jié)點的屬性引用鏈(如:window.foo.bar[2].baz)。

下面的動態(tài)圖顯示了分離節(jié)點的處理過程:

image

例子:嘗試這個例子detached nodes你可以查看節(jié)點在Timeline中的生命周期抖拴,然后拍堆快照來找到分離的節(jié)點燎字。

問:直接占用內(nèi)存(Shallow Size)和占用總內(nèi)存(Retained Size)分別代表什么,它們的區(qū)別是什么阿宅?

是這樣的候衍,對象可以在內(nèi)存中以兩種方式存在(be alive) – 直接的被別一個可訪問的(alive)對象保留(window和document對象總是可訪問的)或被原生對象(象DOM對象)隱含的包留引用。后一種方式會因為阻止對象被GC自動回收洒放,而有導(dǎo)制內(nèi)存泄泥漏的可能蛉鹿。對象自身占用的內(nèi)存被稱為直接占用內(nèi)存(通常來說,數(shù)組和字符串會保留更多的直接占用內(nèi)存(shallow size))往湿。

image

一個任意大小的對象可以通過阻止其它對象內(nèi)存被回收在保留很大的內(nèi)存使用妖异。當(dāng)一個對象被刪除后(它造成的一些依賴就無法被引用了)能夠釋放的內(nèi)存的大小被稱有占用總內(nèi)存(retained size)惋戏。

問:constructor和retained字段下有很多的數(shù)據(jù)。我應(yīng)該從哪開始調(diào)查我是的否遇到了內(nèi)存泄漏呢他膳?

一般來說最好是從通過retainers排序的第一個對象開始响逢,retainers之間是通過距離排序的(是指到window對象的距離)包吝。

image

距離最短的對象有可能是首選的可能導(dǎo)致內(nèi)存泄漏的對象聘惦。

問:Summary, Comparison, Dominators 和 Containment這些視圖之間的不同是什么?

你可以通過切換視圖來體驗它們的區(qū)別谢肾。

image
  • Summary(概要)視圖能幫你通過構(gòu)造函數(shù)分組尋找對象(和對象的內(nèi)存使用)蟀俊。該視圖對找出DOM內(nèi)存泄漏很有幫助钦铺。
  • Comparison(對照)視圖能夠通過顯示哪些對象內(nèi)存被正確的回收了來搜尋內(nèi)存泄漏。通常在一個操作前后記錄兩個(或更多)的內(nèi)存使用快照欧漱。它是通過察看釋放的內(nèi)存和引用數(shù)目的差導(dǎo)來察看是否有內(nèi)存泄漏职抡,并找到原因。
  • Containment(控制)視圖對對象結(jié)構(gòu)有更好的展示误甚,幫助我們分析全局作用域(如 window)中對象引用情況來找到是什么保留了這些對象缚甩。它能讓你分析閉包并深入到對象更深層去查看。
  • Dominators(支配者)視圖能用來幫助我們確認(rèn)沒有多余的對象還掛在某個位置(如那些被引用了的)窑邦,和確認(rèn)對象的刪除/垃圾回收真正起了作用擅威。

問:堆分析儀中的constructor(一組)內(nèi)容代表什么?

image
  • (global property) - 全局對象(像 ‘window’)和引用它的對象之間的中間對象冈钦。如果一個對象由構(gòu)造函數(shù)Person生成并被全局對象引用郊丛,那么引用路徑就是這樣的:[global] > (global property) > Person。這跟一般的直接引用彼此的對象不一樣瞧筛。我們用中間對象是有性能方面的原因厉熟,全局對象改變會很頻繁,非全局變量的屬性訪問優(yōu)化對全局變量來說并不適用较幌。
  • (roots) - constructor中roots的內(nèi)容引用它所選中的對象揍瑟。它們也可以是由引擎自主創(chuàng)建的一些引用。這個引擎有用于引用對象的緩存乍炉,但是這些引用不會阻止引用對象被回收绢片,所以它們不是真正的強引用(FIXME)。
  • ****(closure)** - 一些函數(shù)閉包中的一組對象的引用**
  • (array, string, number, regexp) - 一組屬性引用了Array,String,Number或正則表達(dá)式的對象類型
  • (compiled code) - 簡單來說岛琼,所有東西都與compoled code有關(guān)底循。Script像一個函數(shù),但其實對應(yīng)了<script>的內(nèi)容槐瑞。SharedFunctionInfos (SFI)是函數(shù)和compiled code之間的對象熙涤。函數(shù)通常有內(nèi)容,而SFIS沒有(FIXME)。
  • ****HTMLDivElement, HTMLAnchorElement, DocumentFragment 等 – 你代碼中對elements或document對象的引用灭袁。

在你的程序的生命周期中生成的很多其它的對象猬错,包括事件監(jiān)聽器或自定義對象,可以在下面的controllers中找到:

image

問:我在做內(nèi)存分析時需要關(guān)閉Chrome里可能會產(chǎn)生影響的什么功能么茸歧?

我們建議在用Chrome DevTools做內(nèi)存分析時,你可以使用關(guān)閉所有擴展功能的隱身模式显沈,或設(shè)置用戶文件夾為(--user-data-dir="")后再打開Chrome软瞎。

image

應(yīng)用,擴展甚至console中的記錄都會對你的分析有潛在的影響拉讯,如果你想讓你的分析可靠的話涤浇,禁用這些吧。

寫在最后的話

今天的JavaScript引擎已經(jīng)具有很強的能力魔慷,能夠自動回收代碼產(chǎn)生的內(nèi)存垃圾只锭。就是說,它們只能做到這樣了院尔,但我們的應(yīng)用仍然被證明會因為邏輯錯誤而產(chǎn)生內(nèi)存泄漏蜻展。使用相應(yīng)的工具來找到應(yīng)用的瓶頸,記住邀摆,不要靠猜 – 測試它纵顾。

轉(zhuǎn)自http://www.codeceo.com/article/chrome-javascript-memory.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市栋盹,隨后出現(xiàn)的幾起案子施逾,更是在濱河造成了極大的恐慌,老刑警劉巖例获,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汉额,死亡現(xiàn)場離奇詭異,居然都是意外死亡榨汤,警方通過查閱死者的電腦和手機蠕搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來件余,“玉大人讥脐,你說我怎么就攤上這事√淦鳎” “怎么了旬渠?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長端壳。 經(jīng)常有香客問我告丢,道長,這世上最難降的妖魔是什么损谦? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任岖免,我火速辦了婚禮岳颇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颅湘。我一直安慰自己话侧,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布闯参。 她就那樣靜靜地躺著瞻鹏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹿寨。 梳的紋絲不亂的頭發(fā)上新博,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音脚草,去河邊找鬼赫悄。 笑死,一個胖子當(dāng)著我的面吹牛馏慨,可吹牛的內(nèi)容都是我干的埂淮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼熏纯,長吁一口氣:“原來是場噩夢啊……” “哼同诫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起樟澜,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤误窖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秩贰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霹俺,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年毒费,在試婚紗的時候發(fā)現(xiàn)自己被綠了丙唧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡觅玻,死狀恐怖想际,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溪厘,我是刑警寧澤胡本,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站畸悬,受9級特大地震影響侧甫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一披粟、第九天 我趴在偏房一處隱蔽的房頂上張望咒锻。 院中可真熱鬧,春花似錦守屉、人聲如沸惑艇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敦捧。三九已至,卻和暖如春碰镜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背习瑰。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工绪颖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甜奄。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓柠横,卻偏偏與公主長得像,于是被迫代替她去往敵國和親课兄。 傳聞我的和親對象是個殘疾皇子牍氛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容