Chrome开发者工具之JavaScript内存分析

澳门新葡亰手机版 19

内存泄漏是指计算机可用内存的逐渐减少。当程序持续无法释放其使用的临时内存时就会发生。JavaScript的web应用也会经常遇到在原生应用程序中出现的内存相关的问题,如泄漏和溢出,web应用也需要应对垃圾回收停顿

澳门新葡亰手机版 1

尽管JavaScript使用垃圾回收进行自动内存管理,但有效的(effective)内存管理依然很重要。在这篇文章中我们将探讨分析JavaScript
web应用中的内存问题。在学习有关特性时请确保尝试一下相关案例以提高你对这些工具在实践中如何工作的认识。请阅读内存
101(Memory
101)页面来帮助你熟悉这篇文章中用到的术语。注意:我们将要用到的某些特性目前仅对Chrome
Canary版浏览器可用。我们推荐使用这个版本来获得最佳的工具,以分析你的应用程序的内存问题。

Timeline可以解决你的程序为什么很缓慢,主要从三个层面来获取分析数据:

简介:Timeline主要处理包括处理DOM事件,页面布局渲染或者向屏幕绘制元素这些应用。

左上角的四个按钮依次:

1、点击原点开始录制,这时我们就开始页面操作,弹出框开始记录,点击finish即可结束;

2、会清除你现有的记录会话,以便开始一个新的会话;

3、将会对显示的详细信息进行过滤,只显示那些完成耗时超过15ms(可选)的记录;

4、将会强迫V8完成一轮的垃圾回收,在调试中它很有用;

最下边一层四个可选按钮:

网络和HTML解析(蓝色),JavaScript(黄色),样式重计算和布局(紫色)以及绘画和合成(绿色)事件;

3中模式:帧模式、事件模式、内存模式

注意下图capture的四个按钮,Memory点击后,就是内存模式了,所以它是混合在帧模式和事件模式中的,如果想进一步分析内存的话,可以在打开Profiler版面好好研究~

澳门新葡亰手机版 2

帧模式中最重要的就是右边的参考线,大于30fps时比较流畅,再大于60fps人眼分辨不出,但是小于30fps时就会有明显的卡顿感。然后我们可以点击鼠标在矩形集中的地方,定位问题。

基本用法:1)点击录制->开始页面动画->结束录制;2)通过查看柱状图记录出哪些柱比较高(性能差);3)点击柱图定位事件记录,结合详情数据找出性能卡顿的原因;

澳门新葡亰手机版 3

通过事件模式,可以看出哪些操作带了应能损耗;

事件模式是以事件为导向,观察录制间操作的事件经过,方便定位哪个操作占用的事件比较频繁。同时结合内存面板,可以看得出哪个事件消耗的内存最大,有没有合理地进行垃圾回收(GC)。

澳门新葡亰手机版 4

内存模式,浅蓝色区域,当然越小越好

如果内存出现锯齿状,如下

澳门新葡亰手机版 5

在你空闲之后,内存似乎从没有真正的降下来,那么说明你创造了太多的垃圾。应该是存在内存泄露

那么选择profiles工具来分析内存的问题

澳门新葡亰手机版 6

Take Heap
Snapshot
的选项可以让我们在怀疑点之前和之后获取内存的快照,得到当时程序中活动的Javascript对象(以及DOM节点)在内存中的分布。

要使用这个功能,点击‘Start’,重复你怀疑(出现你发现的那些信息的时刻)会引起内存泄露的动作,这时记录下第一个快照。
接下来点击record按钮 ☻ 来记录第二个快照,这次不需要与程序进行交互

Summary视图适用于DOM泄漏,而Comparison视图擅长于发现内存泄漏的原因

澳门新葡亰手机版 7

现在你看到的信息是在profile之间创建的对象。信息的差集可以让你对比垃圾收集所删除的内存是否匹配上对象的创建所花费的内存。点击特定的构造函数可以在面板下面的对象的retaining
tree视图看到更多信息。

一个典型的应用场景是试图发现一个你已经删除或者断开关联的一个DOM节点是否任然存在。一旦你发现了造成内存占用的代码,你就可以添加必要的代码来清除那么你不需要的相关对象。

使用DevTools中的堆分析仪时

澳门新葡亰手机版 8

后面两列的标题Shallow和Reetained Size是什么意思?

Shallow
Size:直接占用内存(不包括引用的对象占用的内存),对象本身占用的内存;字符串和数组常常会在渲染器内存中存储主要数据部分,仅仅在JavaScript对象栈中暴露一个很小的包装对象。

渲染器内存指你分析的页面在渲染的过程中所用到的所有内存:页面本身的内存

  • 页面中的JS堆用到的内存 +
    页面触发的相关工作进程(workers)中的JS堆用到的内存。

Retained Size
:包括引用的对象所占用的内存,一个对象一但删除后它引用的依赖对象就不能被GC根(GC
root)
引用到,它们所占用的内存就会被释放,一个对象占用总内存包括这些依赖对象所占用的内存。

堆是由各种互相关联的对象组成的网状结构。

那么具体要怎么分析呢?各位看官还是移步这里看更专业的文章

你需要思考的问题

总体来说,当你觉得你遇到了内存泄漏问题时,你需要思考三个问题:

  • 我的页面是否占用了过多的内存? – Timeline内存查看工具(Timeline
    memory
    view) 和 Chrome任务管理(Chrome
    task
    manager) 能帮助你确认你是否使用了过多的内存。Memory
    view
    能跟踪页面渲染过程中DOM节点计数,documents文档计数和JS事件监听计数。作为一个经验法则:避免对不再需要用到的DOM元素的引用,移除不需要的事件监听并且在存储你可能不会用到的大块数据时要留意。
  • 我的页面有没有内存泄漏? – 对象分配跟踪(Object allocation
    tracker)通过实时查看JS对象的分配来帮助你定位泄漏。你也可以使用堆分析仪(Heap
    Profiler)生成JS堆快照,通过分析内存图和比较快照之间的差异,来找出没有被垃圾回收清理掉的对象。
  • 我的页面垃圾强制回收有多频繁? –
    如果你的页面垃圾回收很频繁,那说明你的页面可能内存使用分配太频繁了。Timeline内存查看工具(Timeline
    memory
    view) 能够帮助你发现感兴趣的停顿。

澳门新葡亰手机版 9

术语和基本概念

本小节介绍在内存分析时使用的常用术语,这些术语在为其它语言做内存分析的工具中也适用。这里的术语和概念用在了堆分析仪(Heap
Profiler)UI工具和相关的文档中。

这些能够帮助我们熟悉如何有效的使用内存分析工具。如果你曾用过像Java、.NET等语言的内存分析工具的话,那么这将是一个复习。

对象大小(Object sizes)

把内存想象成一个包含基本类型(像数字和字符串)和对象(关联数组)的图表。它可能看起来像下面这幅一系列相关联的点组成的图。

澳门新葡亰手机版 10

一个对象有两种使用内存的方法:

  • 对象自身直接使用
  • 隐含的保持对其它对象的引用,这种方式会阻止垃圾回收(简称GC)对那些对象的自动回收处理。

当你使用DevTools中的堆分析仪(Heap
Profiler,用来分析内存问题的工具,在DevTools的”Profile”标签下)时,你可能会惊喜的发现一些显示各种信息的栏目。其中有两项是:直接占用内存(Shallow
Size)
占用总内存(Retained Size),那它们是什么意思呢?

澳门新葡亰手机版 11

直接占用内存(Shallow Size,不包括引用的对象占用的内存)

这个是对象本身占用的内存。

典型的JavaScript对象都会有保留内存用来描述这个对象和存储它的直接值。一般,只有数组和字符串会有明显的直接占用内存(Shallow
Size)。但字符串和数组常常会在渲染器内存中存储主要数据部分,仅仅在JavaScript对象栈中暴露一个很小的包装对象。

渲染器内存指你分析的页面在渲染的过程中所用到的所有内存:页面本身的内存 +
页面中的JS堆用到的内存 +
页面触发的相关工作进程(workers)中的JS堆用到的内存。然而,通过阻止垃圾自动回收别的对象,一个小对象都有可能间接占用大量的内存。

占用总内存(Retained Size,包括引用的对象所占用的内存)

一个对象一但删除后它引用的依赖对象就不能被GC根(GC
root)
引用到,它们所占用的内存就会被释放,一个对象占用总内存包括这些依赖对象所占用的内存。

GC根是由控制器(handles)组成的,这些控制器(不论是局部还是全局)是在建立由build-in函数(native
code)到V8引擎之外的JavaScript对象的引用时创建的。所有这些控制器都能够在堆快照的GC
roots(GC根)
 > Handle scope 和 GC roots >Global
handlers
中找到。如果不深入了解浏览器的实现原理,在这篇文章中介绍这些控制器可能会让人不能理解。GC根和控制器你都不需要过多关心。

有很多内部的GC根对用户来说都是不重要的。从应用的角度来说有下面几种情况:

  • Window 全局对象
    (所有iframe中的)。在堆快照中有一个distance字段,它是从window对象到达对应对象的最短路径长度。
  • 由所有document能够遍历到的DOM节点组成的文档DOM树。不是所有节点都会被对应的JS引用,但有JS引用的节点在document存在的情况下都会被保留。
  • 有很多对象可能是在调试代码时或者DevTools
    console中(比如:console中的一些代码执行结束后)创建出来的。

注意:我们推荐用户在创建堆快照时,不要在console中执行代码,也不要启用调试断点。

内存图由一个根部开始,可能是浏览器的window对象或Node.js模块Global对象。这些对象如何被内存回收不受用户的控制。

澳门新葡亰手机版 12

不能被GC根遍历到的对象都将被内存回收。

注意:直接占用内存和占用总内存字段中的数据是用字节表示的。

对象的占用总内存树

之前我们已经了解到,堆是由各种互相关联的对象组成的网状结构。在数字领域,这种结构被称为或内存图。图是由边缘(edges)连接着的节点(nodes)组成的,他们都被贴了标签。

  • 节点(Nodes) (或对象)
    节点的标签名是由创建他们的构造(constructor)函数的名称确定
  • 边缘(Edges) 标签名就是属性名

本文档的后面你将了解到如何使用堆分析仪生成快照。从下图的堆分析仪生成的快照中,我们能看到距离(distance)这个字段:是指对象到GC根的距离。如果同一个类型的所有对象的距离都一样,而有一小部分的距离却比较大,那么就可能出了些你需要进行调查的问题了。

澳门新葡亰手机版 13

支配对象(Dominators)

支配对象就像一个树结构,因为每个对象都有一个支配者。一个对象的支配者可能不会直接引用它支配的对象,就是说,支配对象树结构不是图中的生成树。

澳门新葡亰手机版 14

在上图中:

  • 节点1支配节点2
  • 节点2支配节点3,4和6
  • 节点3支配节点5
  • 节点5支配节点8
  • 节点6支配节点7

在下图的例子中,节点#3#10的支配者,但#7也在每个从GC到#10的路经中都出现了。像这样,如果B对象在每个从根节点到A对象的路经中都出现,那么B对象就是A对象的支配对象。

澳门新葡亰手机版 15

V8介绍

在本节,我们将描述一些内存相关的概念,这些概念是和V8
JavaScript虚拟机
(V8 VM
或VM)有关的。当分析内存时,了解这些概念对理解堆快照是有帮助的。

JavaScript对象描述

有三个原始类型:

  • 数字(Numbers) (如 3.14159..)
  • 布尔值(Booleans) (true或false)
  • 字符型(Strings) (如 ‘Werner Heisenberg’)

它们不会引用别的值,它们只会是叶子节点或终止节点。

数字(Numbers)以下面两种方式之一被存储:

  • 31位整数直接值,称做:小整数(small integers)(SMIs),或
  • 堆对象,引用为堆值。堆值是用来存储不适合用SMI形式存储的数据,像双精度数(doubles),或者当一个值需要被打包(boxed)时,如给这个值再设置属性值。

字符型数据会以下面两种方式存储:

  • VM堆,或
  • 外部的渲染器内存中。这时会创建一个包装对象用来访问存储的位置,比如,Web页面包存的脚本资源和其它内容,而不是直接复制至VM堆中。

新创建的JavaScript对象会被在JavaScript堆上(或VM堆)分配内存。这些对象由V8的垃圾回收器管理,只要还有一个强引用他们就会在内存中保留。

本地对象是所有不在JavaScript堆中的对象,与堆对象不同的是,在它们的生命周期中,不会被V8垃圾加收器处理,只能通过JavaScript包装对象引用。

连接字符串是由一对字符串合并成的对象,是合并后的结果。连接字符串只在有需要时合并。像一连接字符串的子字符串需要被构建时。

比如:如果你连接ab,你得到字符串(a,
b)这用来表示连接的结果。如果你之后要再把这个结果与d连接,你就得到了另一个连接字符串((a,
b), d)。

数组(Arrays) –
数组是数字类型键的对象。它们在V8引擎中存储大数据量的数据时被广泛的使用。像字典这种有键-值对的对象就是用数组实现的。

一个典型的JavaScript对象可以通过两种数组类型之一的方式来存储:

  • 命名属性,和
  • 数字化的元素

如果只有少量的属性,它们会被直接存储在JavaScript对象本身中。

Map –
一种用来描述对象类型和它的结构的对象。比如,maps会被用来描述对象的结构以实现对对象属性的快速访问

对象组

每个本地对象组都是由一组之间相互关联的对象组成的。比如一个DOM子树,每个节点都能访问到它的父元素,下一个子元素和下一个兄弟元素,它们构成了一个关联图。需要注意的是本地元素没有在JavaScript堆中表现-这就是它们的大小是零的原因,而它的包装对象被创建了。

每个包装对象都会有一个到本地对象的引用,用来传递对这些本地对象的操作。这些本地对象也有到包装对象的引用。但这并不会创造无法收回的循环,GC是足够智能的,能够分辨出那些已经没有引用包装对象的本地对象并释放它们的。但如果有一个包装对象没有被释放那它将会保留所有对象组和相关的包装对象。

先决条件和有用提示

Chrome 任务管理器

注意: 当使用Chrome做内存分析时,最好设置一个洁净的测试环境

打开Chrome的内存管理器,观察内存字段,在一个页面上做相关的操作,你可以很快定位这个操作是否会导致页面占用很多内存。你可以从Chrome菜单
> 工具或按Shift + Esc,找到内存管理器。

澳门新葡亰手机版 16

打开后,在标头右击选用 JavasScript使用的内存 这项。

澳门新葡亰手机版,通过DevTools Timeline来定位内存问题

解决问题的第一步就是要能够证明问题存在。这就需要创建一个可重现的测试来做为问题的基准度量。没有可再现的程序,就不能可靠的度量问题。换句话说如果没有基准来做为对比,就无法知道是哪些改变使问题出现的。

时间轴面版(Timeline
panel)
对于发现程序什么时候出了问题很用帮助。它展示了你的web应用或网站加载和交互的时刻。所有的事件:从加载资源到解JavaScript,样式计算,垃圾回收停顿和页面重绘。都在时间轴上表示出来了。

当分析内存问题时,时间轴面版上的内存视图(Memory view)能用来观察:

  • 使用的总内存 – 内存使用增长了么?
  • DOM节点数
  • 文档(documents)数
  • 注册的事件监听器(event listeners)数

澳门新葡亰手机版 17

更多的关于在内存分析时,定位内存泄漏的方法,请阅Zack Grossbart的Memory
profiling with the Chrome
DevTools

证明一个问题的存在

首先要做的事情是找出你认为可能导致内存泄漏的一些动作。可以是发生在页面上的任何事件,鼠标移入,点击,或其它可能会导致页面性能下降的交互。

在时间轴面版上开始记录(Ctrl+E 或
Cmd+E)然后做你想要测试的动作。想要强制进行垃圾回收点面版上的垃圾筒图标()。

下面是一个内存泄漏的例子,有些点没有被垃圾回收:

澳门新葡亰手机版 18

如果经过一些反复测试后,你看到的是锯齿状的图形(在内存面版的上方),说明你的程序中有很多短时存在的对象。而如果一系列的动作没有让内存保持在一定的范围,并且DOM节点数没有返回到开始时的数目,你就可以怀疑有内存泄漏了。

澳门新葡亰手机版 19

一旦确定了存在内存上的问题,你就可以使用分析面板(Profiles
panel)
上的堆分析仪(heap profiler)来定位问题的来源。

例子: 尝试一下memory
growth的例子,能帮助你有效的练习通过时间轴分析内存问题。