偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

JavaScript 內存泄漏排查方法

開發(fā) 前端
本文主要介紹了如何通過 Devtools 的 Memory 內存工具排查 JavaScript 內存泄漏問題。先介紹了一些相關概念,說明了 Memory 內存工具的使用方式,然后介紹了堆快照的分析方式,說明如何通過分析堆快照找到泄漏的 JavaScript 代碼,最后列舉了一些 JavaScript 內存泄漏的排查案例。

一、概述

本文主要介紹了如何通過 Devtools 的 Memory 內存工具排查 JavaScript 內存泄漏問題。先介紹了一些相關概念,說明了 Memory 內存工具的使用方式,然后介紹了堆快照的分析方式,說明如何通過分析堆快照找到泄漏的 JavaScript 代碼,最后列舉了一些 JavaScript 內存泄漏的排查案例。



二、概念說明

1、內存泄漏

內存泄漏(Memory Leak)是指程序中已動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢,甚至系統(tǒng)崩潰等嚴重后果。

簡單來說就是,按照業(yè)務邏輯,本該被回收的對象,可能因為某些代碼的實現(xiàn)不合理,導致對象沒有被及時回收,進而對象占用的內存無法釋放,導致內存的浪費。

2、Memory 常用功能


  • 快照查看方式:主要有摘要和對比
  • 類篩選器:可以過濾構造函數(shù),但是不能過濾對象名
    例如,要查看包含video的構造函數(shù)名:

3、堆快照視圖

3.1 摘要視圖


  • 構造函數(shù):JS構造函數(shù),以及由JS引擎、框架或庫創(chuàng)建的構造函數(shù)
  • 距離 (distance):與 GC root 之間的距離,如果某節(jié)點沒有 distance,通常說明該節(jié)點即將被 gc 回收
  • 卷影大小/淺層大小 (shadow size):對象本身的大小
    淺層大小可以直觀地看出內存具體地分配給哪些對象了
  • 保留的大小 (retained size):對象釋放后可以回收的內存大?。▍⒖糲hrome的定義)
  • 保留的大小說明了哪些對象導致了內存占用高,但不一定是這個對象本身內存高,可能是因為它引用的對象占用內存高。
  • 有時候某個對象的 retained size,不等于其所有屬性的 retained size 之和。因為該對象的多個屬性都被回收之后,才能讓這多個屬性引用的對象回收,所以這些被引用的對象的大小不會計入此對象中。例如這里的 DOMTimer@1199111936,保留的大小為 1468B ,但其引用的一個context@2160207 保留的大小為 124520B,已經(jīng)大于了 DOMTimer 的保留大小了。因為這個context不止被這個 DOMTimer 引用,還被其他的 DOMTimer 引用,需要這些 DOMTimer 全部被回收之后,才能把這個 context 回收,因此它的大小不計入 DOMTimer 中。換句話說,如果只有 DOMTimer 引用這個 context,那 context 的保留大小就會計入 DOMTimer 中。

3.2 對比視圖

  • 新建:快照查看方式選擇比較后,若是新建列中有一個點,則表示是在兩個快照之間新建的對象

  • 已刪除:同理,若在已刪除列有一個點,表示在兩個快照之間刪除的對象
  • 增量:指對象增加的數(shù)量
  • 分配大?。涸趦蓚€快照之間分配的大小
  • 已釋放:在兩個快照之間釋放的內存大小
  • 大小增量:在兩個快照之間增長的淺層大小

4、構造函數(shù)和對象

4.1 構造函數(shù)

  • 在上半部分的構造函數(shù)這一列中,第一層都是構造函數(shù),后面的數(shù)字表示這個類的對象數(shù)。例如下圖中,當前的Object數(shù)量為89160
  • 展開某一個對象后,子元素表示該對象的屬性,例如這里Object@207683的屬性包含aweme_list、map
  • 對象名的::之后的即對象所屬的類,@符號之后的表示對象id

4.2 對象

在下半部分的對象這一列中,節(jié)點之間的關系為:當前節(jié)點被子節(jié)點引用。例如前3行,aweme_list被一個Object類型的對象H引用,H被一個Context類型的對象context引用,context被一個函數(shù)類型的對象get $$引用。

藍色的鏈接可以跳轉到源代碼,源代碼中會以下劃線標注某段代碼:

表示在這段代碼中,當前對象引用了下一個對象。例如下圖中的4784.0ec58630.js:1的某段代碼中,$$()函數(shù)的上下文context引用了H

4.3 查看對象信息

通過鼠標懸停在對象上,可以查看對象信息,不過并不是所有對象都能查看到信息,顯示"預覽不可用"的對象可能已經(jīng)被回收了。

4.4 在頁面訪問 dom

  • 如果對象的右邊有個窗口圖標,則表示可以在窗口訪問這個元素,鼠標懸停在對象上,可以查看信息,同時會在頁面高亮該對象,例如這個video標簽是當前視頻所在video標簽

  • 有時候即使有這個窗口圖標,頁面中也不會高亮這個元素(可能已經(jīng)是Detached狀態(tài)了),或者有些元素沒有這個窗口圖標。這個時候如果還想知道這個是什么元素,可以查看其信息,找到其對應的class,然后在“元素”中搜索
    例如這里的Detached HTMLImageElement@2407721對象,它實際上就是頁面中的“搶”標簽:

  • 如果查看元素對象的信息時,顯示"預覽不可用",則暫時沒有辦法找到該元素。此時可以看看它引用了哪些元素或者被哪些元素引用,看看是否能在頁面中查看這些元素,如果可以,再以此推測之前的元素。

5、堆快照常見對象類型

5.1 Detached DOM

  • 如果刪除了某個dom節(jié)點,但仍有變量對此節(jié)點存在引用關系,則這個dom節(jié)點就會變成游離狀態(tài),也就是不存在于document上了。
  • 簡單來說就是,dom節(jié)點已經(jīng)不存在于頁面中了,但仍然被JS對象引用著。

5.2 DOM Timer

定時器。setInterval()和setTimeout()函數(shù)會創(chuàng)建。是最容易出現(xiàn)泄漏的對象之一,寫代碼時,很容易出現(xiàn)創(chuàng)建了定時器但是沒有銷毀的情況,這樣就會導致定時器引用的對象泄漏。

5.3 Context

通常指函數(shù)的上下文

  • 例如以下代碼中,會自動為inlineTestFunc函數(shù)創(chuàng)建一個context對象,該context對象會引用variable
function testFunc(){
  const variable = 'I am refereced by inlineTestFunc()'
  const inlineTestFunc = function () {}
  return inlineTestFunc
}
window.testFunc = testFunc()
  • 如果在其他地方引用了inlineTestFunc()函數(shù),那么variable變量也會同時被引用。

5.4 Closure

閉包:函數(shù)以及其捆綁的周邊環(huán)境狀態(tài)

5.5 Compiled code

運行代碼占用的內存,通常不會出現(xiàn)內存泄漏

5.6 InternalNode

瀏覽器內置對象,通常不需要關注,一方面是因為導致內存泄漏的一般是JS對象,而不是內部對象。另一方面是因為它造成的內存泄漏在前端不好解決。如果確實需要獲得InternalNode的具體對象名,來排查內存泄漏,可以通過在編譯chrome時添加特定參數(shù)來實現(xiàn),可以參考:

三、排查步驟

1、Devtools手動排查

使用 Devtools 的 Memory 內存工具來對 JS 內存泄漏進行排查分析

1.1 復現(xiàn)和堆快照抓取

  1. 確定疑似有內存泄漏的操作,例如抖音PC客戶端中切換視頻、發(fā)送評論、發(fā)送彈幕等。復現(xiàn)操作要盡可能的小,并且最好盡可能地排除其他變量的干擾,這對后續(xù)的問題定位有很大的影響。
  2. 訪問不壓縮代碼的頁面(可選)
  3. 手動或用js腳本寫 puppeteer 在瀏覽器復現(xiàn)
  4. GC 后拍攝快照;執(zhí)行疑似導致內存泄漏的操作;GC 后拍攝快照
    a. 執(zhí)行疑似泄漏的操作時,建議重復執(zhí)行多次,讓上漲的內存大于 30MB 以上,根據(jù)過往經(jīng)驗,最好在100MB~200MB左右(太大的話抓快照太慢了),這樣會比較容易觀察,否則上漲的內存太小,不容易發(fā)現(xiàn)是哪些對象增長。通常情況下,這個快照大小幾乎可以直接看出哪些對象異常。例如這里切換了 50 個視頻,可以明顯地看出 Video 元素的異常:

1.2 篩選泄漏的引用鏈

比較執(zhí)行泄漏操作前和執(zhí)行泄漏操作后的快照,篩選疑似泄漏的引用鏈

1.2.1 篩選方法

手動篩選時,往往很難 100% 確定哪些對象是泄漏的,一般來說只能是懷疑某些對象泄漏,有了懷疑的對象后,按照下一步的方法來分析引用鏈是否合理,如果沒有找到可疑的引用鏈,那么就需要反復進行1.2和1.3步驟,才能更容易找到泄漏的對象。如果說看了好多對象,或者看了幾分鐘都沒看出來有哪些異常,那么可能是測試得到堆快照對比的增量大小太小了,不容易直接看出來,或者是觀察的數(shù)據(jù)類型不好找到泄漏,比如Object、Array就很難篩選出泄漏的對象,這時可以考慮看其他數(shù)據(jù)類型。

1.2.2 優(yōu)先看內存增量大的對象

優(yōu)先看內存增量比較高的數(shù)據(jù),例如某些對象的大小增量為 100M,而其他的就只有 10M 不到,那么優(yōu)先看大小增量為 100M 的。如果都內存增長都差不多,可以繼續(xù)按下面步驟進行排查。

如果某些對象大小增量比較高,說明它們最有可能是泄漏的對象。它們?yōu)槭裁礇]有被回收?可以通過點開對象查看引用的信息,分析該對象整個引用跟蹤鏈路,來找到其中某個不合理的引用。

1.2.3 注意內存占用大的 Detached 元素

如果沒有內存占用相對較高的對象,或者有好幾種數(shù)據(jù)大小增長都差不多,可以從Detached元素入手,Detached元素出現(xiàn)內存泄漏的概率比較大,可以觀察內存占用相對比較高的Detached元素,原因有兩個:

這里需要注意的是,Detached元素的淺層大小通常是很小的,而前面提到過,通過堆快照對比得出的“大小增量”指的是淺層大小增量,所以在堆快照的對比視圖里,Detached元素的大小增量一般都會比較小,它實際造成的泄漏是大于“大小增量”這個值的,那么怎么知道它造成了多少泄漏?可以點開某個元素,可以看到它的保留的大小,這個就反應出了它造成的內存泄漏大小。

  1. 一方面是因為object、array之類的數(shù)據(jù),通常對象數(shù)量非常大(幾萬個是比較常見的),并且有很多是正常對象,而泄漏的對象混雜在其中很難分辨哪些是泄漏的、哪些是正常的,除非泄漏的對象非常多,占據(jù)它們數(shù)量的大部分,或者有個別對象占用的內存特別大時,才比較容易直接觀察到。
  2. 另一方面是Detached元素,通常對象數(shù)較少,并且比較容易出現(xiàn)相似的引用鏈,如果這里面有新的泄漏對象出現(xiàn),很容易發(fā)現(xiàn)這些新增的泄漏對象

1.2.4 注意常見泄漏類型

事件監(jiān)聽(EventListener)、定時器(DOMTimer)、數(shù)組(Array),這是最容易導致內存泄漏的幾種數(shù)據(jù)類型,比如監(jiān)聽事件之后沒有及時取消監(jiān)聽,定時器開啟之后沒有銷毀,數(shù)組元素無限增加,可以專門針對這幾種類型進行排查,并對用到這類對象的代碼格外注意。

  1. 可通過多次復現(xiàn)泄漏操作的方式來確定某些對象或者數(shù)組是否存在泄漏。先抓取快照,鼠標懸停到疑似泄漏的數(shù)組上,查看信息,記錄下數(shù)組長度,然后執(zhí)行一遍疑似導致內存泄漏的操作,再查看數(shù)組長度(這里查看到的對象信息是實時的,所以無需抓快照也能看到當前的數(shù)組詳情),如果數(shù)組增長了,那么就有可能是泄漏的,接下來可以多重復幾次導致泄漏的操作,看看是否按預期增長,如果按預期增長,那么基本可以確定是泄漏對象。(為了確保準確性,可以在記錄長度前先進行 GC)
  2. 下圖中通過查看數(shù)組信息,可以看到數(shù)組長度為 986,執(zhí)行一次疑似泄漏操作后,長度變?yōu)榱?989。因此推測這里每執(zhí)行一次疑似泄漏操作會 +3 個元素,所以推測執(zhí)行 10 次疑似泄漏操作后,會增加 30 個,這里經(jīng)過驗證確實是增加 30 個,并且經(jīng)過一段時間后,依舊沒有減少,所以基本可以確定這個數(shù)組為泄漏對象

1.2.5 注意頻繁出現(xiàn)的引用鏈

內存泄漏通常會引起很多類似的對象無法被銷毀,因此很容易會出現(xiàn)很多對象的引用鏈是一樣的,所以可以在點開某種數(shù)據(jù)類型后(如DOM元素、object、string),多觀察幾個對象的引用鏈,如果某一條類似的引用鏈頻繁出現(xiàn),那么很有可能該引用鏈中出現(xiàn)了泄漏。

  • 例如下面的圖中,10 個Object對象出現(xiàn)了 8 個相同的引用鏈,因此這條引用鏈中很可能存在泄漏,通過不斷測試可以確定引用鏈中的InternalNode是無限增長的,到此可以認為這里是存在內存泄漏的,再對HTMLVideoElement進行分析,最終確定是MediaElementVideoElement存在內存泄漏。

1.3 確定泄漏的對象和代碼

在上一步驟中,篩選出一些疑似泄漏的引用鏈后,開始分析引用鏈的泄漏對象,確定導致泄漏的某個引用。

通常可以先不看InternalNode對象

1.3.1 從業(yè)務邏輯分析哪些是泄漏對象

根據(jù)業(yè)務邏輯來分析哪些對象是不應該存在的,查找創(chuàng)建或者引用了這個對象的代碼片段。可以先搞清楚整個引用鏈存在的原因,通過引用鏈上的對象的信息,或者dom元素信息等,來分析這個引用鏈因為哪一個業(yè)務邏輯而存在,根據(jù)這個業(yè)務邏輯聯(lián)想可能的泄漏情況,比如常見的事件、定時器是否已清除。

  • 例如下圖這個引用鏈,從observerListdomResizeListener這兩個對象名可以推測這里使用了監(jiān)聽器模式,domResizeListener是監(jiān)聽器,當改變頁面大小的時候,通過調用observerList里的觀察者的回調函數(shù),修改某些 dom 的大小。根據(jù)這個代碼邏輯聯(lián)想,懷疑某些地方把一些 dom 元素加入到observerList里了,但是 dom 銷毀的時候沒有取消監(jiān)聽,dom 元素依然存在observerList里,導致無法被回收。

1.3.2 導致泄漏的引用通?!熬嚯x”較大

  1. 導致泄漏的引用比較容易發(fā)生在“距離”較大的地方,也就是距離根節(jié)點比較遠的對象(DOMTimer除外)。距離根節(jié)點較遠的對象,和業(yè)務代碼的相關性比較大,距離根節(jié)點較近的對象,大多都是一些常駐對象,或者是難以回收的對象;而“距離”較大的對象,往往只會被一個對象引用,因為“距離”大的對象往往是業(yè)務代碼創(chuàng)建的,而“距離”較小的對象,通常會被很多對象引用,或者是一些底層的框架之類的,往往不容易出現(xiàn)泄漏。
    a. 例如下圖中,上方的array為泄漏對象,為了避免這個泄漏,從根節(jié)點configdata這條引用鏈路中,回收任意一個對象都能使array被回收,但是如果要回收config,就需要把引用它的playerbound_this、context等都回收才行,一般來說這樣距離根節(jié)點較近的對象是很難回收的,通常也不是造成泄漏的原因,而這里最終泄漏的原因是,array是緩存的數(shù)據(jù),把緩存放進去之后沒有及時清理。(引用鏈中只能看出這個數(shù)組被誰引用,看不出是哪里添加的數(shù)據(jù),需要根據(jù)業(yè)務邏輯和代碼進行分析)

1.3.3 定位引用所在代碼

在對象視圖中的代碼跳轉鏈接,指出了對象被引用的地方,但并不是說就是這一行代碼導致的內存泄漏。例如下圖中泄漏的對象是taskCallback,通過代碼跳轉,指出的代碼是a.taskCallback(),說明taskCallback因為a對象的引用而無法被回收。這里經(jīng)過代碼分析得出的結論是:this.intervalTimer沒有及時銷毀,繼而存在引用intervalTimer->a->taskCallback,而導致taskCallback函數(shù)及其引用的對象泄漏。

四、排查案例

1、監(jiān)聽器泄漏

  • 在抖音PC客戶端中,通過自動化測試發(fā)現(xiàn),刷視頻會出現(xiàn)持續(xù)的內存上漲,所以針對刷視頻這個場景進行內存泄漏排查。在刷視頻之前和刷視頻之后,分別抓取內存快照,選擇“比較”對這兩個快照進行比較,搜索detached元素,選中一個div查看其引用鏈

  • 引用鏈表示從引用該div的對象出發(fā),一直到根節(jié)點的整個鏈路。
  • 通過觀察多個div發(fā)現(xiàn),大部分都存在相同的引用鏈,即和上圖類似。detached元素本身就很可能是泄漏的對象,加上很多detached元素都有相同的引用鏈,所以這個引用鏈很可能存在內存泄漏。通過分析這些引用的名字,可以推測使用了監(jiān)聽器,JS代碼中比較容易出現(xiàn)泄漏的情況有監(jiān)聽器、定時器,這里懷疑是showDisturbLoginPanel這個對象,它被_events引用,推測這是一個事件,通過鼠標懸停到_events上查看內存,showDisturbLoginPanel是一個數(shù)組,里面存放的是函數(shù):

  • 展開函數(shù),可以看到函數(shù)的代碼位置。查看了好幾個函數(shù),發(fā)現(xiàn)這些函數(shù)的位置都是相同的,點開函數(shù):

  • 可以看到是監(jiān)聽事件,由此初步推測:每次刷視頻,都會監(jiān)聽事件,但是沒有及時銷毀。
  • 有了初步推測之后,接下來再復現(xiàn)一次內存泄漏,然后驗證結果是否符合推測:

  • 可以看到,再刷一次視頻后,數(shù)組長度 +3,到這里基本可以確定:showDisturbLoginPanel中引用的函數(shù)沒有釋放而引起泄漏,即事件沒有及時銷毀。

五、參考文檔

1、工具使用

2、排查方法

責任編輯:龐桂玉 來源: 字節(jié)跳動技術團隊
相關推薦

2025-06-26 02:14:00

Java本地內存排查方法

2019-02-20 09:29:44

Java內存郵件

2025-03-03 05:20:00

2021-08-05 15:28:22

JS內存泄漏

2020-06-08 09:18:59

JavaScript開發(fā)技術

2025-02-28 06:23:38

2025-04-02 08:17:42

2025-08-13 13:03:53

內存泄漏場景

2022-12-13 10:59:47

devtoolMemory

2009-06-10 22:03:40

JavaScript內IE內存泄漏

2022-05-26 09:51:50

JavaScrip內存泄漏

2010-07-16 09:11:40

JavaScript內存泄漏

2025-07-28 02:25:00

2018-12-07 10:52:08

內存泄漏方法

2010-09-25 11:07:45

Java內存泄漏

2025-05-06 07:24:24

2024-11-22 09:40:18

Visual內存泄漏內存

2019-12-17 10:01:40

開發(fā)技能代碼

2024-11-21 09:30:38

內存泄漏CPU

2020-11-02 09:48:35

C++泄漏代碼
點贊
收藏

51CTO技術棧公眾號