JavaScript 堆内存分析新工具 OneHeap
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 源码里的HeapGraphNodeedges属性保存了节点间的映射关系,对应 v8 源码的HeapGraphEdgestrings保存了所有的字符串,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 属性
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). |
nodes 和 edges 的对应关系
如果知道某个节点的 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
...
- 每遍历完一个节点,就在
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源码
参考资料
关于
本文相关的源码在: https://github.com/wyvernnot/javascriptperformancemeasurement/tree/gh-pages/heap_snapshot;
本文由OneAPM工程师原创,想阅读更多技术文章,请访问OneAPM官方技术博客。
JavaScript 堆内存分析新工具 OneHeap的更多相关文章
- [BUGCASE]Webpack打包报JavaScript堆内存泄漏的错误
一.问题描述 执行npm run build之后报错: 报错信息: FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript he ...
- JavaScript 性能分析新工具 OneProfile
OneProfile 是一个网页版的小工具,可以用全新的方式展示 JavaScript 性能分析的结果,帮助开发者洞悉函数调用关系,优化应用性能. 点击打开 OneProfile 背景 Chrome ...
- jvm堆内存模型原理分析及堆内存分析工具jhat和MAT的使用超详细教程
- 【JVM】内存分析<一>工具的使用
一. 获取堆快照 1.出现OOME时生成堆dump: #出现 OOME 时生成堆 dump: -XX:+HeapDumpOnOutOfMemoryError #生成堆文件地址: -XX:HeapDum ...
- 利用MAT玩转JVM内存分析(一)
本文首发于公众号:javaadu 尽管JVM提供了自动内存管理的机制,试图降低程序员的开发门槛,确实也实现了这一目标,在日常开发中,我们一般都不需要关心对象的内存释放.JVM大部分都是使用trace算 ...
- Chrome开发者工具之JavaScript内存分析
阅读目录 对象大小(Object sizes) 对象的占用总内存树 支配对象(Dominators) V8介绍 Chrome 任务管理器 通过DevTools Timeline来定位内存问题 内存回收 ...
- Chrome开发者工具之JavaScript内存分析(转)
尽管JavaScript使用垃圾回收进行自动内存管理,但有效的(effective)内存管理依然很重要.在这篇文章中我们将探讨分析JavaScript web应用中的内存问题.在学习有关特性时请确保尝 ...
- Memlab,一款分析 JavaScript 堆并查找浏览器和 Node.js 中内存泄漏的开源框架
Memlab 是一款 E2E 测试和分析框架,用于发现 JavaScript 内存泄漏和优化机会. Memlab 是 JavaScript 的内存测试框架.它支持定义一个测试场景(使用 Puppete ...
- [转载]JavaScript内存分析
https://github.com/CN-Chrome-DevTools/CN-Chrome-DevTools/blob/master/md/Performance-Profiling/javasc ...
随机推荐
- 对ASP.NET Entity FrameWork进行单元测试
添加一个测试用的类库:将Web.config中的connectionstrings节点下的东东复制一份到刚添加的类库的app.config下 使用NUint+TestDriven.net进行测试: 如 ...
- Linux驱动开发之字符设备模板
/***************************** ** 驱动程序模板* 版本:V1* 使用方法(末行模式下):* :%s/xxx/"你的驱动名称"/g********* ...
- Python学习教程(learning Python)--2.3.3 Python函数型参详解
本节讨论Python下函数型参的预设值问题. Python在设计函数时,可以给型参预设缺省值,当用户调用函数时可以不输入实参.如果用户不想使用缺省预设值则需要给型参一一赋值,可以给某些型参赋值或不按型 ...
- Android WebView代理设置方法(API10~21适用)
最近碰到个需求需要在APP中加入代理,HttpClient的代理好解决,但是WebView碰到些问题,然后找到个API10~API21都通用的类,需要用的同学自己看吧,使用方法,直接调用类方法setP ...
- 菜鸟学习Spring——60s让你学会动态代理原理
一.为什么要使用动态代理 当一个对象或多个对象实现了N中方法的时候,由于业务需求需要把这个对象和多个对象的N个方法加入一个共同的方法,比如把所有对象的所有方法加入事务这个时候有三种方法 ...
- 【6.24-AppCan移动开发大会倒计时】科大讯飞来了!
6.24 AppCan移动开发者大会进入倒计时,报名通道即将关闭! 50多家移动圈服务商将出席此次大会,讯飞开放平台也将作为参展商,为参会者带去前沿的语音技术.参会者可现场体验最新连续语音识别技术,识 ...
- andirod
于adnroid老手来说,SDK环境搭建是很简单的,但是对于我这样的小白来说,,,,,走啦很多弯路..特记下.希望对兄弟们有所帮助 因为我也是参考网上的高手知道,所以有的就直接复制啦,,^_^ 想要开 ...
- UEFI双硬盘安装win8.1和Ubuntu14.04
UEFI双硬盘安装win8.1和Ubuntu14.04 安装环境 UEFI启动模式 双GPT硬盘 一个ssd 一个hdd 笔记本已安装win8.1 硬盘启动顺序为: U盘 ssd hdd 光驱 安装方 ...
- TList,TObjectList 使用——资源释放
TOjectList = Class (Tlist); TOjectList继承Tlist,从名字上看就可以知道它是专门为对象列表制作的,那么他到底丰富了那些功能呢? 首先是 TObject 作为对象 ...
- 【BZOJ 1033】 [ZJOI2008]杀蚂蚁antbuster
Description 最近,佳佳迷上了一款好玩的小游戏:antbuster.游戏规则非常简单:在一张地图上,左上角是蚂蚁窝,右下角是蛋糕,蚂蚁会源源不断地从窝里爬出来,试图把蛋糕搬回蚂蚁窝.而你的任 ...