JavaScript 堆内存分析新工具 OneHeap

Stella981
• 阅读 602

OneHeap 关注于运行中的 JavaScript 内存信息的展示,用可视化的方式还原了 HeapGraph,有助于理解 v8 内存管理。

##背景

JavaScript 运行过程中的大部分数据都保存在堆 (Heap) 中,所以 JavaScript 性能分析另一个比较重要的方面是内存,也就是堆的分析。

利用 Chrome Dev Tools 可以生成应用程序某个时刻的堆快照 (HeapSnapshot),它较完整地记录了各种对象和引用的情况,堪称查找内存泄露问题的神器。 和 Profile 结果一样,快照可以被导出成 .heapsnapshot 文件。

JavaScript 堆内存分析新工具 OneHeap

上周发布了工具 OneProfile , 可以用来动态地展示 Profile 的结果,分析各种函数的调用关系。周末我用类似的思路研究了一下 .heapsnapshot 文件,做了这个网页小工具,把 Heap Snapshot 用有向图的方式展现出来。

JavaScript 堆内存分析新工具 OneHeap

##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 对象:

  1. snapshot 属性保存了关于快照的一些基本信息,如 uid,快照名,节点个数等

  2. nodes 保存了是所有节点的 id,name,大小信息等,对应 v8 源码里的 HeapGraphNode

  3. edges 属性保存了节点间的映射关系,对应 v8 源码的 HeapGraphEdge

  4. strings 保存了所有的字符串, nodesedges 中不会直接存字符串,而是存了字符串在 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 是一个 012 的数字,目前的 Chrome 只有 09 这几个属性,它们对应的含义分别是

编号

属性

说明

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

具体做法如下:

  1. 在遍历 nodes 前初始化一个变量 edge_offset,初始值是0,每遍历一个节点都会改变它的值。

  2. 遍历某个节点 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

...
  1. 每遍历完一个节点,就在 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

JavaScript 堆内存分析新工具 OneHeap

朴灵老师的《深入浅出Node.JS》有对 Buffer 的详细介绍,其中提到 Buffer 是 JavaScript 和 C++ 技术结合的典型代表

浏览器

JavaScript 堆内存分析新工具 OneHeap

很明显浏览器下多了 Window 和 Document 对象,而 Detached DOM tree 正是前端内存泄露的高发地。

###Objects

JavaScript 堆内存分析新工具 OneHeap

最密集的那部分的中心是 Object 构造函数,如果把 Object 和 Array 构造函数隐藏,就变成了下面这样

JavaScript 堆内存分析新工具 OneHeap

###MathConstructor

JavaScript 堆内存分析新工具 OneHeap

左上角是例如 自然对数E 这样的常量,v8源码

###正则表达式

JavaScript 堆内存分析新工具 OneHeap

所有的正则表达式实例的 __proto__ 都指向 RegExp 构造函数,同时 RegExp 的 __proto__ 又指向 Object

###Stream

JavaScript 堆内存分析新工具 OneHeap

在 Node.JS 中和 Stream 相关的几个类的设计和 Java 类似,都使用到装饰器的设计模式,层层嵌套, 例如 v8源码

##参考资料 Heap Profiling

了解 JavaScript 应用程序中的内存泄漏

##关于 本文相关的源码在: https://github.com/wyvernnot/javascriptperformancemeasurement/tree/gh-pages/heap_snapshot;

本文系 OneAPM 工程师原创。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
2年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
JDK8中JVM堆内存划分
一:JVM中内存JVM中内存通常划分为两个部分,分别为堆内存与栈内存,栈内存主要用运行线程方法存放本地暂时变量与线程中方法运行时候须要的引用对象地址。JVM全部的对象信息都存放在堆内存中。相比栈内存,堆内存能够所大的多,所以JVM一直通过对堆内存划分不同的功能区块实现对堆内存中对象管理。堆内存不够最常见的错误就是OOM(OutOf
Wesley13 Wesley13
2年前
Java内存分析工具MAT
MAT是一个强大的内存分析工具,可以快捷、有效地帮助我们找到内存泄露,减少内存消耗分析工具。内存中堆的使用情况是应用性能监测的重点,而对于堆的快照,可以dump出来进一步分析,总的来说,一般我们对于堆dump快照有三种方式:添加启动参数发生OOM时自动dump:java应用的启动参数一般最好都加上XX:HeapDumpOnOutOfMe
Stella981 Stella981
2年前
JVM 面试
1、内存模型以及分区,需要详细到每个区放什么。通俗的说,Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在Java虚拟机启动时创建,非堆内存(NonheapMemory)是在JVM堆之外的内存。简单来说,堆是Java代码可及的内
Wesley13 Wesley13
2年前
Java中的OutOfMemoryError的各种情况及解决和JVM内存结构
在JVM中内存一共有3种:Heap(堆内存),NonHeap(非堆内存)\3\和Native(本地内存)。\1\堆内存是运行时分配所有类实例和数组的一块内存区域。非堆内存包含方法区和JVM内部处理或优化所需的内存,存放有类结构(如运行时常量池、字段及方法结构,以及方法和构造函数代码)。本地内存是由操作系统管理的虚拟内存。当一个应用内存不足时
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这