摘要:關注于運行中的內存信息的展示,用可視化的方式還原了,有助于理解內存管理。背景運行過程中的大部分數據都保存在堆中,所以性能分析另一個比較重要的方面是內存,也就是堆的分析。上周發布了工具,可以用來動態地展示的結果,分析各種函數的調用關系。
背景OneHeap 關注于運行中的 JavaScript 內存信息的展示,用可視化的方式還原了 HeapGraph,有助于理解 v8 內存管理。
JavaScript 運行過程中的大部分數據都保存在堆 (Heap) 中,所以 JavaScript 性能分析另一個比較重要的方面是內存,也就是堆的分析。
利用 Chrome Dev Tools 可以生成應用程序某個時刻的堆快照 (HeapSnapshot),它較完整地記錄了各種對象和引用的情況,堪稱查找內存泄露問題的神器。 和 Profile 結果一樣,快照可以被導出成 .heapsnapshot 文件。
上周發布了工具 OneProfile , 可以用來動態地展示 Profile 的結果,分析各種函數的調用關系。周末我用類似的思路研究了一下 .heapsnapshot 文件,做了這個網頁小工具,把 Heap Snapshot 用有向圖的方式展現出來。
OneHeap 名字的由來There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
目前還沒有時間想一個高端、大氣、上檔次的名字,因為我供職的公司名叫 OneAPM ( 省去軟廣1000字,總之做性能監控很牛),所以就取名 OneHeap 啦。 它是 Toolkit 里的第二個。
如何生成 Heap Snapshot 文件 瀏覽器使用 Chrome 打開 測試頁面 按 F12 打開 Devtools,切換到 Profiles 頁,選擇 Take Heap Snapshot。稍等片刻,在生成的 Snapshot 上點擊右鍵可以導出,文件后綴一般是 .heapsnapshot。
Node.JS如果你是 Node.JS 工程師,可以安裝 heapdump 這個很有名的模塊。
https://github.com/bnoordhuis/node-heapdump
上面兩種方法都可以生成 .heapsnapshot 文件,這個是用來測試的 nodejs.heapsnapshot
理解 .heapsnapshot 文件格式打開測試用的 nodejs.heapsnapshot 文件,這是一個很大的 JSON 對象:
snapshot 屬性保存了關于快照的一些基本信息,如 uid,快照名,節點個數等
nodes 保存了是所有節點的 id,name,大小信息等,對應 v8 源碼里的 HeapGraphNode
edges 屬性保存了節點間的映射關系,對應 v8 源碼的 HeapGraphEdge
strings 保存了所有的字符串, nodes 和 edges 中不會直接存字符串,而是存了字符串在 strings 中的索引
堆快照其實是一個有向圖的數據結構,但是 .heapsnapshot 文件在存儲的過程中使用了數組來存儲圖的結構,這一設計十分巧妙而且減少了所需磁盤空間的大小。
nodes 屬性nodes 是一個很長一維的數組,但是為了閱讀方便,v8 在序列化的時候會自動加上換行。按照 v8 版本的不同,可能是5個一行,也可能是6個一行,如果是 6 個一行,則多出來的一個 trace_node_id 屬性。
下標 | 屬性 | 類型 |
---|---|---|
n | type | number |
n+1 | name | string |
n+2 | id | number |
n+3 | self_size | number |
n+4 | edge_count | number |
其中 type 是一個 0~12 的數字,目前的 Chrome 只有 0~9 這幾個屬性,它們對應的含義分別是
編號 | 屬性 | 說明 |
---|---|---|
0 | hidden | Hidden node, may be filtered when shown to user. |
1 | array | An array of elements. |
2 | string | A string. |
3 | object | A JS object (except for arrays and strings). |
4 | code | Compiled code. |
5 | closure | Function closure. |
6 | regexp | RegExp. |
7 | number | Number stored in the heap. |
8 | native | Native object (not from V8 heap). |
9 | synthetic | Synthetic object, usualy used for grouping snapshot items together. |
10 | concatenated | string Concatenated string. A pair of pointers to strings. |
11 | sliced string | Sliced string. A fragment of another string. |
12 | symbol | A Symbol (ES6). |
edges 也是一個一維數組,長度要比 nodes 大好幾倍,并且相對于 nodes 要復雜一些:
下標 | 屬性 | 類型 |
---|---|---|
n | type | number |
n+1 | nameorindex | stringornumber |
n+2 | to_node | node |
其中 type 是一個 0~6 的數字:
編號 | 屬性 | 說明 |
---|---|---|
0 | context | A variable from a function context. |
1 | element | An element of an array |
2 | property | A named object property. |
3 | internal | A link that can"t be accessed from JS,thus, its name isn"t a real property name (e.g. parts of a ConsString). |
4 | hidden | A link that is needed for proper sizes calculation, but may be hidden from user. |
5 | shortcut | A link that must not be followed during sizes calculation. |
6 | weak | A weak reference (ignored by the GC). |
如果知道某個節點的 id,是沒有辦法直接從 edges 中查出和它相鄰的點的,因為 edges 并不是一個 from-to 的 Hash。想知道從一個節點出發 可到達那些節點,需要遍歷一次 nodes。
具體做法如下:
在遍歷 nodes 前初始化一個變量 edge_offset,初始值是0,每遍歷一個節點都會改變它的值。
遍歷某個節點 Nx 的過程中:
從 Nx 出發的第一條 Edge
edges[ edge_offset ] 是 Edge 的類型 edges[ edge_offset +1 ] 是 Edge 的名稱或下標 edges[ edge_offset +2 ] 是 Edge 指向的對象的節點類型在 `nodes` 里的索引
從 Nx 出發的第2條 Edge
edges[ edge_offset + 3 ] ............ 是下一個 Edge edges[ edge_offset + 5 ]
從 Nx 出發,一共有 edge_count 條 Edge
...
3. 每遍歷完一個節點,就在 edge_offset 上加 3 x edge_count,并回到步驟 2,直到所有節點都遍歷完
步驟1到3 用偽代碼表示就是:
edge_offset=0 // 遍歷每一個節點 for(node in nodes){ // edges 下標從 edge_offset 到 edge_offset + 3 x edge_count 都是 node 可以到達的點 edge_offset+= 3 x node.edge_count }
以上就是 .heapsnapshot 的文件格式定義了,基于這些發現,在結合一個前端繪圖的庫,就可以可視化的展示 Heap Snapshot 了。
OneHeap 使用說明鏈接地址
使用 Chrome 打開: OneHeap
一些有意思的截圖@1
Node.JS
樸靈老師的《深入淺出Node.JS》有對 Buffer 的詳細介紹,其中提到 Buffer 是 JavaScript 和 C++ 技術結合的典型代表
瀏覽器
很明顯瀏覽器下多了 Window 和 Document 對象,而 Detached DOM tree 正是前端內存泄露的高發地。
Objects最密集的那部分的中心是 Object 構造函數,如果把 Object 和 Array 構造函數隱藏,就變成了下面這樣
MathConstructor左上角是例如 自然對數E 這樣的常量,v8源碼
正則表達式所有的正則表達式實例的 __proto__都指向 RegExp 構造函數,同時 RegExp 的 __proto__又指向 Object
Stream在 Node.JS 中和 Stream 相關的幾個類的設計和 Java 類似,都使用到裝飾器的設計模式,層層嵌套, 例如 v8源碼
參考資料Heap Profiling
了解 JavaScript 應用程序中的內存泄漏
關于本文相關的源碼在: https://github.com/wyvernnot/javascriptperformancemeasurement/tree/gh-...;
本文由OneAPM工程師原創,想閱讀更多技術文章,請訪問OneAPM官方技術博客。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85842.html
摘要:加上內部函數被返回,被其他對象引用,形成了閉包,因此對應的變量對象存在于閉包函數的作用域鏈中。因此訪問次數越多,費用越高,頁面性能就會受到很大影響。盡管還主動執行了一次操作,曲線也沒有下降。 副標題:常見的JavaScript內存泄露 這是關于JavaScript內存泄露相關的序列文章中一篇。由于時間有限更新進度會有點慢,但會持續更新的。自己也在學習中,難免對某些知識點的理解不是很正確...
摘要:筆者寫的數據結構與算法之美系列用的語言是,旨在入門數據結構與算法和方便以后復習。這應該是目前較為簡單的十大經典排序算法的文章講解了吧。比如原本在的前面,而,排序之后,在的后面十大經典排序算法冒泡排序思想冒泡排序只會操作相鄰的兩個數據。 showImg(https://segmentfault.com/img/bVbvHet); 1. 前言 算法為王。想學好前端,先練好內功,內功不行,就...
摘要:垃圾回收內存管理實踐先通過一個來看看在中進行垃圾回收的過程是怎樣的內存泄漏識別在環境里提供了方法用來查看當前進程內存使用情況,單位為字節中保存的進程占用的內存部分,包括代碼本身棧堆。 showImg(https://segmentfault.com/img/remote/1460000019894672?w=640&h=426);作者 | 五月君Node.js 技術棧 | https:...
摘要:在運行腳本時,需要顯示的指定對象。大對象區每一個區域都是由一組內存頁構成的。這里是唯一擁有執行權限的內存區。換句話說,是該對象被之后所能回收到內存的總和。一旦活躍對象已被移出,則在舊的半空間中剩下的任何死亡對象被丟棄。 內存管理 本文以V8為背景 對之前的文章進行重新編輯,內容做了很多的調整,使其具有邏輯更加緊湊,內容更加全面。 1. 基礎概念 1.1 生命周期 不管什么程序語言,內存...
摘要:引擎對堆內存中的對象進行分代管理新生代存活周期較短的對象,如臨時變量字符串等。內存泄漏對于持續運行的服務進程,必須及時釋放不再用到的內存。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第一期,本周的主題是調用堆棧,今天是第4天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃...
閱讀 1629·2023-04-25 16:29
閱讀 955·2021-11-15 11:38
閱讀 2292·2021-09-23 11:45
閱讀 1420·2021-09-22 16:03
閱讀 2538·2019-08-30 15:54
閱讀 1204·2019-08-30 10:53
閱讀 2603·2019-08-29 15:24
閱讀 1102·2019-08-26 12:25