主頁 > 企業開發 > Chrome 瀏覽器垃圾回識訓制與記憶體泄漏分析

Chrome 瀏覽器垃圾回識訓制與記憶體泄漏分析

2020-10-19 04:33:58 企業開發

Chorme 瀏覽器中的垃圾回收和記憶體泄漏

垃圾回收

通常情況下,垃圾資料回收分為手動回收自動回收兩種策略,

手動回收策略,何時分配記憶體、何時銷毀記憶體都是由代碼控制的,
自動回收策略,產生的垃圾資料是由垃圾回收器來釋放的,并不需要手動通過代碼來釋放,

JavaScript 中呼叫堆疊中的資料回收

JavaScript 引擎會通過向下移動 ESP(記錄當前執行狀態的指標) 來銷毀該函式保存在堆疊中的執行背景關系,

JavaScript 堆中的資料回收

在 V8 中會把堆分為新生代老生代兩個區域,新生代中存放的是生存時間短的物件,老生代中存放的生存時間久的物件,

新生區通常只支持 1~8M 的容量,而老生區支持的容量就大很多了,對于這兩塊區域,V8 分別使用兩個不同的垃圾回收器,以便更高效地實施垃圾回收,

  • 副垃圾回收器,主要負責新生代的垃圾回收,
  • 主垃圾回收器,主要負責老生代的垃圾回收,

不論什么型別的垃圾回收器,它們都有一套共同的執行流程,

  1. 第一步是標記空間中活動物件和非活動物件,所謂活動物件就是還在使用的物件,非活動物件就是可以進行垃圾回收的物件,
  2. 第二步是回收非活動物件所占據的記憶體,其實就是在所有的標記完成之后,統一清理記憶體中所有被標記為可回收的物件,
  3. 第三步是做記憶體整理,一般來說,頻繁回收物件后,記憶體中就會存在大量不連續空間,我們把這些不連續的記憶體空間稱為記憶體碎片,,當記憶體中出現了大量的記憶體碎片之后,如果需要分配較大連續記憶體的時候,就有可能出現記憶體不足的情況,所以最后一步需要整理這些記憶體碎片,(這步其實是可選的,因為有的垃圾回收器不會產生記憶體碎片).
新生代中垃圾回收

新生代中用Scavenge 演算法來處理,把新生代空間對半劃分為兩個區域,一半是物件區域,一半是空閑區域,新加入的物件都會存放到物件區域,當物件區域快被寫滿時,就需要執行一次垃圾清理操作,

在垃圾回收程序中,首先要對物件區域中的垃圾做標記;標記完成之后,就進入垃圾清理階段,副垃圾回收器會把這些存活的物件復制到空閑區域中,同時它還會把這些物件有序地排列起來,所以這個復制程序,也就相當于完成了記憶體整理操作,復制后空閑區域就沒有記憶體碎片了,

完成復制后,物件區域與空閑區域進行角色翻轉,也就是原來的物件區域變成空閑區域,原來的空閑區域變成了物件區域,這樣就完成了垃圾物件的回收操作,同時這種角色翻轉的操作還能讓新生代中的這兩塊區域無限重復使用下去.

為了執行效率,一般新生區的空間會被設定得比較小,也正是因為新生區的空間不大,所以很容易被存活的物件裝滿整個區域,為了解決這個問題,JavaScript 引擎采用了物件晉升策略,也就是經過兩次垃圾回收依然還存活的物件,會被移動到老生區中,

老生代中的垃圾回收

老生代中用標記 - 清除(Mark-Sweep)的演算法來處理,首先是標記程序階段,標記階段就是從一組根元素開始,遞回遍歷這組根元素(遍歷呼叫堆疊),在這個遍歷程序中,能到達的元素稱為活動物件,沒有到達的元素就可以判斷為垃圾資料.然后在遍歷程序中標記,標記完成后就進行清除程序,它和副垃圾回收器的垃圾清除程序完全不同,這個的清楚程序是洗掉標記資料,

清除演算法后,會產生大量不連續的記憶體碎片,而碎片過多會導致大物件無法分配到足夠的連續記憶體,于是又產生了標記 - 整理(Mark-Compact)演算法,這個標記程序仍然與標記 - 清除演算法里的是一樣的,但后續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然后直接清理掉端邊界以外的記憶體,從而讓存活物件占用連續的記憶體塊,

全停頓

由于 JavaScript 是運行在主執行緒之上的,一旦執行垃圾回收演算法,都需要將正在執行的 JavaScript 腳本暫停下來,待垃圾回收完畢后再恢復腳本執行,我們把這種行為叫做全停頓

在 V8 新生代的垃圾回收中,因其空間較小,且存活物件較少,所以全停頓的影響不大,但老生代就不一樣了,如果執行垃圾回收的程序中,占用主執行緒時間過久,主執行緒是不能做其他事情的,比如頁面正在執行一個 JavaScript 影片,因為垃圾回收器在作業,就會導致這個影片在垃圾回收程序中無法執行,這將會造成頁面的卡頓現象,

為了降低老生代的垃圾回收而造成的卡頓,V8 將標記程序分為一個個的子標記程序,同時讓垃圾回收標記和 JavaScript 應用邏輯交替進行,直到標記階段完成,我們把這個演算法稱為增量標記(Incremental Marking)演算法.

使用增量標記演算法,可以把一個完整的垃圾回收任務拆分為很多小的任務,這些小的任務執行時間比較短,可以穿插在其他的 JavaScript 任務中間執行,這樣當執行上述影片效果時,就不會讓用戶因為垃圾回收任務而感受到頁面的卡頓了,

記憶體泄漏

不再用到的記憶體,沒有及時釋放,就叫做記憶體泄漏(memory leak),

記憶體泄漏發生的原因

  1. 快取

有時候為了方便資料的快捷復用,我們會使用快取,但是快取必須有一個大小上限才有用,高記憶體消耗將會導致快取突破上限,因為快取內容無法被回收,

  1. 佇列消費不及時
    當瀏覽器佇列消費不及時時,會導致一些作用域變數得不到及時的釋放,因而導致記憶體泄漏,

  2. 全域變數

除了常規設定了比較大的物件在全域變數中,還可能是意外導致的全域變數,如:

function foo(arg) {
    bar = "this is a hidden global variable";
}

在函式中,沒有使用 var/let/const 定義變數,這樣實際上是定義在window上面,變成了window.bar
再比如由于this導致的全域變數:

function foo() {
    this.bar = "this is a hidden global variable";
}
foo()

這種函式,在window作用域下被呼叫時,函式里面的this指向了window,執行時實際上為window.bar=xxx,這樣也產生了全域變數,

  1. 計時器中參考沒有清除

先看如下代碼:

var someData = https://www.cnblogs.com/LuckyWinty/p/getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someData));
    }
}, 1000);

這里定義了一個計時器,每隔1s把一些資料寫到Node節點里面,但是當這個Node節點被洗掉后,這里的邏輯其實都不需要了,可是這樣寫,卻導致了計時器里面的回呼函式無法被回收,同時,someData里的資料也是無法被回收的,

  1. 閉包

看以下這個閉包:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);

每次呼叫 replaceThingtheThing 會創建一個大陣列和一個新閉包(someMethod)的新物件,同時,變數 unused 是一個參考 originalThing(theThing) 的閉包,閉包的作用域一旦創建,它們有同樣的父級作用域,作用域是共享的,

someMethod 可以通過 theThing 使用,someMethodunused 分享閉包作用域,盡管 unused 從未使用,它參考的 originalThing 迫使它保留在記憶體中(防止被回收),

因此,當這段代碼反復運行,就會看到記憶體占用不斷上升,垃圾回收器(GC)并無法降低記憶體占用,

本質上,閉包的鏈表已經創建,每一個閉包作用域攜帶一個指向大陣列的間接的參考,造成嚴重的記憶體泄漏,

  1. 事件監聽

例如,Node.js 中 Agent 的 keepAlive 為 true 時,可能造成的記憶體泄漏,當 Agent keepAlive 為 true 的時候,將會復用之前使用過的 socket,如果在 socket 上添加事件監聽,忘記清除的話,因為 socket 的復用,將導致事件重復監聽從而產生記憶體泄漏,

記憶體泄漏的識別方法

  1. 使用 Chrome 任務管理器實時監視記憶體使用
    打開 chrome 瀏覽器,點擊右上角主選單,選擇更多工具->任務管理器,這樣就開啟了任務管理器面板,然后再右鍵點擊任務管理器的表格標題并啟用 JavaScript使用的記憶體,能看到這樣的面板:

下面兩列可以告訴您與頁面的記憶體使用有關的不同資訊:
GitHub

  1. 記憶體占用空間(Memory) 串列示原生記憶體,DOM 節點存盤在原生記憶體中, 如果此值正在增大,則說明正在創建 DOM 節點,
  2. JavaScript使用的記憶體(JavaScript Memory) 串列示 JS 堆,此列包含兩個值, 您感興趣的值是實時數字(括號中的數字),實時數字表示您的頁面上的可到達物件正在使用的記憶體量, 如果此數字在增大,要么是正在創建新物件,要么是現有物件正在增長,

當你頁面穩定下來之后,這兩個的值還在上漲,你就可以查一查是否記憶體泄漏了,

  1. 利用chrome 時間軸記錄可視化記憶體泄漏

Performance(時間軸)能夠面板直觀實時顯示JS記憶體使用情況、節點數量、監聽器數量等,

打開 chrome 瀏覽器,調出除錯面板(DevTools),點擊Performance選項(低版本是Timeline),勾選Memory復選框,一種比較好的做法是使用強制垃圾回收開始和結束記錄,在記錄時點擊 Collect garbage 按鈕 (強制垃圾回收按鈕) 可以強制進行垃圾回收,
所以錄制順序可以這樣:開始錄制前先點擊垃圾回收-->點擊開始錄制-->點擊垃圾回收-->點擊結束錄制,
面板介紹如圖:
GitHub
錄制結果如圖:
GitHub
首先,從圖中我們可以看出不同顏色的曲線代表的含義,這里主要關注JS堆記憶體、節點數量、監聽器數量,滑鼠移到曲線上,可以在左下角顯示具體資料,在實際使用程序中,如果您看到這種 JS 堆大小或節點大小不斷增大的模式,則可能存在記憶體泄漏,

  1. 使用堆快照發現已分離 DOM 樹的記憶體泄漏

只有頁面的 DOM 樹或 JavaScript 代碼不再參考 DOM 節點時,DOM 節點才會被作為垃圾進行回收, 如果某個節點已從 DOM 樹移除,但某些 JavaScript 仍然參考它,我們稱此節點為“已分離”,已分離的 DOM 節點是記憶體泄漏的常見原因,

同理,調出除錯面板,點擊Memory,然后選擇Heap Snapshot,然后點擊進行錄制,錄制完成后,選中錄制結果,在 Class filter 文本框中鍵入 Detached,搜索已分離的 DOM 樹,
以這段代碼為例:

<html>
<head>
</head>
<body>
<button id="createBtn">增加節點</button>
<script> 
var detachedNodes;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('createBtn').addEventListener('click', create);
</script>
</body>
</html>

點擊幾下,然后記錄,可以得到以下資訊:
GitHub
舊版的面板,還會有顏色標注,黃色的物件實體表示它被JS代碼參考,紅色的物件實體表示被黃色節點參考的游離節點,上圖是新版本的,不會有顏色標識,但是還是可以一個個來看,如上圖,點開節點,可以看到下面的參考資訊,上面可以看出,有個HTMLUListElement(ul節點)被window.detachedNodes參考,再結合代碼,原來是沒有加var/let/const宣告,導致其成了全域變數,所以DOM無法釋放,

  1. 按函式調查記憶體分配
    打開面板,點擊JavaScript Profiler,如果沒看到這個選項,你可以點除錯面板右上角的三個點,選擇more tools,然后選擇,

ps: chrome 舊版的瀏覽器,這個功能在 Profiles 里面,點Record Allocation Profile即可.

操作步驟:點start->在頁面進行你要檢測的操作->點stop,
GitHub
DevTools 按函式顯示記憶體分配明細,默認視圖為 Heavy (Bottom Up),將分配了最多記憶體的函式顯示在最上方,還有函式的位置,你可以看看是哪些函式占用記憶體較多,

避免記憶體泄漏的方法

  1. 少用全域變數,避免意外產生全域變數
  2. 使用閉包要及時注意,有Dom元素的參考要及時清理,
  3. 計時器里的回呼沒用的時候要記得銷毀,
  4. 為了避免疏忽導致的遺忘,我們可以使用 WeakSetWeakMap結構,它們對于值的參考都是不計入垃圾回識訓制的,表示這是弱參考,
    舉個例子:
const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

這種情況下,一旦消除對該節點的參考,它占用的記憶體就會被垃圾回識訓制釋放,Weakmap 保存的這個鍵值對,也會自動消失,

基本上,如果你要往物件上添加資料,又不想干擾垃圾回識訓制,就可以使用 WeakMap,

參考資料

  • 極客時間《瀏覽器作業原理與實踐》
  • https://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/
  • https://developers.google.com/web/tools/chrome-devtools/memory-problems?hl=zh-cn

最后

  • 歡迎加我微信(winty230),拉你進技術群,長期交流學習...
  • 歡迎關注「前端Q」,認真學前端,做個有專業的技術人...

GitHub

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/179481.html

標籤:JavaScript

上一篇:Javascript定時器

下一篇:vue使用 封裝websocket心跳包

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more