[转载]JavaScript内存分析

https://github.com/CN-Chrome-DevTools/CN-Chrome-DevTools/blob/master/md/Performance-Profiling/javascript-memory-profiling.md#heading=h.3gfl4k8caz0kjavascript

JavaScript内存分析

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

尽管JavaScript使用垃圾回收进行自动内存管理,但有效的(effective)内存管理依然很重要。在这篇文章中咱们将探讨分析JavaScript web应用中的内存问题。在学习有关特性时请确保尝试一下相关案例以提升你对这些工具在实践中如何工做的认识。html5

请阅读内存 101(Memory 101)页面来帮助你熟悉这篇文章中用到的术语。java

注意:咱们将要用到的某些特性目前仅对Chrome Canary版浏览器可用。咱们推荐使用这个版原本得到最佳的工具,以分析你的应用程序的内存问题。node

你须要思考的问题

整体来讲,当你以为你遇到了内存泄漏问题时,你须要思考三个问题:git

  • 个人页面是否占用了过多的内存? - Timeline内存查看工具(Timeline memory view)Chrome任务管理(Chrome task manager) 能帮助你确认你是否使用了过多的内存。Memory view 能跟踪页面渲染过程当中DOM节点计数,documents文档计数和JS事件监听计数。做为一个经验法则:避免对再也不须要用到的DOM元素的引用,移除不须要的事件监听而且在存储你可能不会用到的大块数据时要留意。github

  • 个人页面有没有内存泄漏? - 对象分配跟踪(Object allocation tracker)经过实时查看JS对象的分配来帮助你定位泄漏。你也可使用堆分析仪(Heap Profiler)生成JS堆快照,经过分析内存图和比较快照之间的差别,来找出没有被垃圾回收清理掉的对象。web

  • 个人页面垃圾强制回收有多频繁? - 若是你的页面垃圾回收很频繁,那说明你的页面可能内存使用分配太频繁了。Timeline内存查看工具(Timeline memory view) 可以帮助你发现感兴趣的停顿。正则表达式

术语和基本概念

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

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

对象大小(Object sizes)

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

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

  • 对象自身直接使用

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

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

直接占用内存(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 scopeGC roots > Global handlers中找到。若是不深刻了解浏览器的实现原理,在这篇文章中介绍这些控制器可能会让人不能理解。GC根和控制器你都不须要过多关心。

有不少内部的GC根对用户来讲都是不重要的。从应用的角度来讲有下面几种状况:

  • Window 全局对象 (全部iframe中的)。在堆快照中有一个distance字段,它是从window对象到达对应对象的最短路径长度。

  • 由全部document可以遍历到的DOM节点组成的文档DOM树。不是全部节点都会被对应的JS引用,但有JS引用的节点在document存在的状况下都会被保留。

  • 有不少对象多是在调试代码时或者DevTools console中(好比:console中的一些代码执行结束后)建立出来的。

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

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

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

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

对象的占用总内存树

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

  • 节点(Nodes) (或对象) 节点的标签名是由建立他们的构造(constructor)函数的名称肯定

  • 边缘(Edges) 标签名就是属性名

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

支配对象(Dominators)

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

在上图中:

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

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

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,找到内存管理器。

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

经过DevTools Timeline来定位内存问题

解决问题的第一步就是要可以证实问题存在。这就须要建立一个可重现的测试来作为问题的基准度量。没有可再现的程序,就不能可靠的度量问题。换句话说若是没有基准来作为对比,就没法知道是哪些改变使问题出现的。

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

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

  • 使用的总内存 - 内存使用增加了么?

  • DOM节点数

  • 文档(documents)数

  • 注册的事件监听器(event listeners)数

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

证实一个问题的存在

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

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

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

若是通过一些反复测试后,你看到的是锯齿状的图形(在内存面版的上方),说明你的程序中有不少短时存在的对象。而若是一系列的动做没有让内存保持在必定的范围,而且DOM节点数没有返回到开始时的数目,你就能够怀疑有内存泄漏了。

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

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

内存回收

内存回收器(像V8中的)须要可以定位哪些对象是活的(live),而那些被认为是死的(垃圾)的对象是没法引用到的(unreachable)

若是垃圾回收 (GC)由于JavaScript执行时有逻辑错误而没有可以回收到垃圾对象,这些垃圾对象就没法再被从新回收了。像这样的状况最终会让你的应用愈来愈慢。

好比你在写代码时,有的变量和事件监听器已经用不到了,可是却仍然被有些代码引用。只要引用还存在,那被引用的对象就没法被GC正确的回收。

当你的应用程序在运行中,有些DOM对象可能已经更新/移除了,要记住检查引用了DOM对象的变量并将其设null。检查可能会引用到其它对象(或其它DOM元素)的对象属性。双眼要盯着可能会愈来愈增加的变量缓存。

堆分析仪

拍一个快照

在Profiles面板中,选择Take Heap Snapshot,而后点击Start或者按Cmd + E或者Ctrl + E:

快照最初是保存在渲染器进程内存中的。它们被按需导入到了DevTools中,当你点击快照按钮后就能够看到它们了。当快照被载入DevTools中显示后,快照标题下面的数字显示了可以被引用到的(reachable)JavaScript对象占有内存总数。

例子:尝试一下garbage collection in action的例子,在时间轴(Timeline)面板中监控内存的使用。

清除快照

点击Clear all按钮图标(),就能清除掉全部快照:

注意:关闭DevTools窗口并不能从渲染内存中删除掉收集的快照。当从新打开DevTools后,以前的快照列表还在。

记住咱们以前提到的,当你生成快照时你能够强制执行在DevTools中GC。当咱们拍快照时,GC是自动执行的。在时间轴(Timeline)中点击垃圾桶(垃圾回收)按钮()就能够轻松的执行垃圾回收了。

例子:尝试一下scattered objects并用堆分析仪(Heap Profiler)分析它。你能够看到(对象)项目的集合。

切换快照视图

一个快照能够根据不一样的任务切换视图。能够经过如图的选择框切换:

下面是三个默认视图:

  • Summary(概要) - 经过构造函数名分类显示对象;

  • Comparison(对照) - 显示两个快照间对象的差别;

  • Containment(控制) - 可用来探测堆内容;

Dominators(支配者)视图能够在Settings面板中开启 - 显示dominators tree. 能够用来找到内存增加点。

经过不一样颜色区分对象

对象的属性和属性值有不一样的类型并自动的经过颜么进行了区分。每一个属性都是如下四种之一:

  • a:property - 经过名称索引的普通属性,由.(点)操做符,或[](中括号)引用,如["foo bar"];

  • 0:element - 经过数字索引的普通属性,由[](中括号)引用;

  • a:context var - 函数内的属性,在函数上下文内,经过名称引用;

  • a:system prop - 由JavaScript VM 添加的属性,JavaScript代码不能访问。

命名为System的对象没有对应的JavaScript类型。它们是JavaScript VM对象系统内置的。V8将大多数内置对象和用户JS对象放在同一个堆中。但它们只是V8的内部对象。

视图详解

Summary view(概要视图)

打开一个快照,默认是以概要视图显示的,显示了对象总数,能够展开显示具体内容:
Initially, a snapshot opens in the Summary view, displaying object totals, which can be expanded to show instances:

第一层级是"整体"行,它们显示了:

  • Constructor(构造函数)表示全部经过该构造函数生成的对象

  • 对象的实例数在Objects Count列上显示

  • Shallow size列显示了由对应构造函数生成的对象的shallow sizes(直接占用内存)总数

  • Retained size列展现了对应对象所占用的最大内存

  • Distance列显示的是对象到达GC根的最短距离

展开一个整体行后,会显示全部的对象实例。没一个实例的直接占用内存和占用总内存都被相应显示。@符号后的数字不对象的惟一ID,有了它你就能够逐个对象的在不一样快照间做对比。

例子:尝试这个例子(在新tab标签中打开)来了解如何使用概要视图。

记住黄色的对象被JavaScript引用,而红色的对象是由黄色背景色引用被分离了的节点。

Comparison view(对照视图)

该视图用来对照不一样的快照来找到快照之间的差别,来发现有内存泄漏的对象。来证实对应用的某个操做没有形成泄漏(好比:通常一对操做和撤消的动做,像找开一个document,而后关闭,这样是不会形成泄漏的),你能够按如下的步骤尝试:

  1. 在操做前拍一个堆快照;

  2. 执行一个操做(作你认为会形成泄漏的动做);

  3. 撤消以前的操做(上一个操做相反的操做,多重复几回);

  4. 拍第二个快照,将视图切换成对照视图,并同快照1进行对比。

在对照视图下,两个快照之间的不一样就会展示出来了。当展开一个总类目后,增长和删除了的对象就显示出来了:

例子:尝试例子(在新tab标签中打开)来了解如何使用对照视图来定位内存泄漏。

Containment view(控制视图)

控制视图能够称做对你的应用的对象结构的"鸟瞰视图(bird's eys view)"。它能让你查看function内部,跟你的JavaScript对象同样的观察VM内部对象,能让你在你的应用的很是低层的内存使用状况。

该视图提供了几个进入点:

  • DOMWindow 对象 - 这些对象是JavaScript代码的"全局"对象;

  • GC根 - VM的垃圾回收器真正的GC根;

  • Native对象 - 浏览器对象对"推入"JavaScript虚拟机中来进行自动操做,如:DOM节点,CSS规则(下一节会有详细介绍。)

下图是一个典型的控制视图:

例子:尝试例子(在新tab标签中打开)来了解如何使用控制视图来查看闭包内部和事件处理。

关于闭包的建议

给函数命名对你在快照中的闭包函数间做出区分会很用帮助。如:下面的例子中没有给函数命名:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

而下面这个有给函数命名:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

例子:尝试这个例子why eval is evil来分析内存中闭包的影响。你可能也对尝试下面这个例子,记录heap allocations(堆分配)有兴趣。

揭露DOM内存泄漏

这个工具独一无二的一点是展现了浏览器原生对象(DOM节点,CSS规则)和JavaScript对象之间的双向引用。这能帮助你发现由于忘记解除引用游离的DOM子节点而致使的难以发觉的内存泄漏。

DOM内存泄漏可能会超出你的想象。看下下面的例子 - #tree对象何时被GC呢?

var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW can be #tree GC

#leaf表明了对它的父节点的引用(parentNode)它递归引用到了#tree,因此,只有当leafRef被nullified后#tree表明的整个树结构才会被GC回收。

例子:尝试leaking DOM nodes来了解哪里DOM节点会内存泄漏并如何定位。你也能够看一下这个例子:DOM leaks being bigger than expected

查看Gonzalo Ruiz de Villa的文章Finding and debugging memory leaks with the Chrome DevTools来阅读更多关于DOM内存泄漏和内存分析的基础。

原生对象在Summary和Containment视呼中更容易找到 - 有它们专门的类目:

例子:尝试下这个例子(在新tab标签中打开)来了解如何将DOM树分离。

支配者视图(Dominators view)

支配者视图显示了堆图的支配者树。支配者视图跟控制(Containment)视图很像,可是没有属性名。这是由于支配者可能会是一个没有直接引用的对象,就是说这个支配者树不是堆图的生成树。但这是个有用的视图能帮助咱们很快的定位内存增加点。

注意:在Chrome Canary中,支配者视图可以在DevTools中的Settings > Show advanced heap snapshot properties 开启,重启DevTools生效。

例子:尝试这个例子(在新tab标签中打开)来练习如何找到内存增加点。能够进一步尝试下一个例子retaining paths and dominators

对象分配跟踪器

对象跟踪器整合了heap profiler的快照增量更新分析和Timeline面板的记录。跟其它工具同样,记录对象的堆配置须要启动记录,执行一系列操做,而后中止记录而后进行分析。

对象跟踪器不间断的记录堆快照(频率达到了每50毫秒!),结束时记录最后一个快照。该堆分配分析器显示对象在哪被建立并定位它的保留路径。

开启并使用对象分析器

开始使用对象分析器:

  1. 确认你使用的是最新版的Chrome Canary

  2. 打开DeveTools并点击齿轮图标(译者:没明白这步有什么用)。

  3. 如今,打开Profiler面板,你就能看到"Record Heap Allocations"的选项。

上面的柱条表示在堆中生成的新对象。高度就对应了相应对象的大小,它的颜色表示了这个对象是否在最后拍的那个快照中还在:蓝色柱表示在timeline最后这个对象还在,灰色柱表示这个对象在timeline中生成,但结束前已经被内存回收了。

上面的例子中,一个动做执行了10次。同一个程序保留了5个对象,因此最后5个蓝色柱条被保留了。但这最后留下的柱存在潜在的问题。你能够用timeline上的滑动条缩小到那个特定的快照并找到这个分配的对象。

点击一个堆中的对象就能在堆快照的下面部分显示它的保留总内存树。检查这个对象的保留总内存树可以给你足够的信息来了解为何这个对象没有被回收,而后你就能对代码作相应的修改来去掉没必要要的引用。

内存分析FAQ

问:我不能看到对象的全部属性,我也看到它们的非字符串值!为何?

并不是全部属性都完整的保存在JavaScript堆中。其中有些是经过执行原生代码的getters方法来获取的。这些属性没有在堆快照中捕获,是为了防止对getters方法的调用和避免程序状态的改变,若是这些getters方法不是"纯(pure)"的functions。一样,非字符串的值,如数字,没有被捕获是为了减小快照的大小。

问:@符号后面的数字是什么意思 - 是地址仍是ID呢?这个ID值真的是惟一的么?

这是对象ID。显示对象的地址没有意义,由于一个对象会在垃圾回收的时候被移除。这些对象IDs是真正的IDs - 就是说,它们在不一样的快照间是惟一表示的。这样就能够的堆状态间进行精确的对比。维持这些IDs会给GC流程增长额外的开支,但这仅在记录第一次堆快照时分配 - 若是堆分析仪没有用到,就不会有额外的开支。

问:"死"(没法引用到的)对象被包含在快照中了么?

没有,只有能够引用到的对象才会显示在快照中。并且,拍快照前都会先自动执行GC操做。

注意:在写这篇文章的时候,咱们计划在拍快照的时候再也不GC,防止堆尺寸的减小。如今已是这样了,但垃圾对象依然显示在快照以外。

问:GC根是由什么组成的?

由不少部分组成:

  • 原生对象图;

  • 符号表;

  • VM线程中的栈;

  • 编辑缓存;

  • 控制器上下文;

  • 全局控制器。

问:我得知可使用Heap Profiler和Timeline Memory view来检测内存泄漏。但我应该先用哪一个工具呢?

Timeline面版,是在你第一次使用你的页面发现速度变慢了时用来论断过多的内存使用。网站变慢是比较典型的内存泄漏的信号,但也多是其它的缘由 - 多是有渲染或网络传输方面的瓶颈,因此要确保解决你网页的真正问题。

论断是不是内存问题,就打开Timeline面板和Memory标签。点击record按钮,而后在你的应用上重复几回你认为可能致使内存泄漏的操做。中止记录。你应用的内存使用图就生成出来了。若是内存的使用一直在增加(而没有相应的降低),这就代表你的应用可能有内存泄漏了。

通常一个正常的应用的内存使用图形是锯齿状的,由于内存使用后又会被垃圾回收器回收。不用担忧这种锯齿形 - 由于老是会由于JavaScript而有内存的消耗,甚至一个空的requestAnimationFrame也会形成这种锯齿形,这是没法避免的。只要不是那种分配了持续不少内存的形状,那就代表生成了不少内存垃圾。

上图的增加线是须要你警戒的。在诊断分析的时候Memory标签中的DOM node counter,Document counter和Event listener count也是颇有用的。DOM节点数是使用的原生内存不会影响JavaScript内存图。

一旦你确认你的应用有内存泄漏,堆分析仪就能够用来找到内存泄漏的地方。

问:我发现堆快照中有的DOM节点的数字是用红色标记为"Detached DOM tree",而其它的是黄色的,这是什么意思呢?

你会发现有不一样的颜色。红色的节点(有着深色的背景)没有从JavaScript到它们的直接的引用,但它们是分离出来的DOM结构的一部分,因此他们仍是在内存中保留了。有可能有一个节点被JavaScript引用到了(多是在闭包中或者一个变量),这个引用会阻止整个DOM树被内存回收。

黄色节点(黄色背景)有JavaScript的直接引用。在同一个分离的DOM树中查看一个黄色的节点来定位你的JavaScript的引用。就可能看到从DOM window到那个节点的属性引用链(如:window.foo.bar[2].baz)。

下面的动态图显示了分离节点的处理过程:

例子:尝试这个例子detached nodes你能够查看节点在Timeline中的生命周期,而后拍堆快照来找到分离的节点。

问:直接占用内存(Shallow Size)和占用总内存(Retained Size)分别表明什么,它们的区别是什么?

是这样的,对象能够在内存中以两种方式存在(be alive) - 直接的被别一个可访问的(alive)对象保留(window和document对象老是可访问的)或被原生对象(象DOM对象)隐含的包留引用。后一种方式会由于阻止对象被GC自动回收,而有导制内存泄泥漏的可能。对象自身占用的内存被称为直接占用内存(一般来讲,数组和字符串会保留更多的直接占用内存(shallow size))。

一个任意大小的对象能够经过阻止其它对象内存被回收在保留很大的内存使用。当一个对象被删除后(它形成的一些依赖就没法被引用了)可以释放的内存的大小被称有占用总内存(retained size)。

问:constructor和retained字段下有不少的数据。我应该从哪开始调查我是的否遇到了内存泄漏呢?

通常来讲最好是从经过retainers排序的第一个对象开始,retainers之间是经过距离排序的(是指到window对象的距离)。

距离最短的对象有多是首选的可能致使内存泄漏的对象。

问:Summary, Comparison, Dominators 和 Containment这些视图之间的不一样是什么?

你能够经过切换视图来体验它们的区别。

  • Summary(概要)视图能帮你经过构造函数分组寻找对象(和对象的内存使用)。该视图对找出DOM内存泄漏颇有帮助。

  • Comparison(对照)视图可以经过显示哪些对象内存被正确的回收了来搜寻内存泄漏。一般在一个操做先后记录两个(或更多)的内存使用快照。它是经过察看释放的内存和引用数目的差导来察看是否有内存泄漏,并找到缘由。

  • Containment(控制)视图对对象结构有更好的展现,帮助咱们分析全局做用域(如 window)中对象引用状况来找到是什么保留了这些对象。它能让你分析闭包并深刻到对象更深层去查看。

  • Dominators(支配者)视图能用来帮助咱们确认没有多余的对象还挂在某个位置(如那些被引用了的),和确认对象的删除/垃圾回收真正起了做用。

问:堆分析仪中的constructor(一组)内容表明什么?

  • (global property) - 全局对象(像 'window')和引用它的对象之间的中间对象。若是一个对象由构造函数Person生成并被全局对象引用,那么引用路径就是这样的:[global] > (global property) > Person。这跟通常的直接引用彼此的对象不同。咱们用中间对象是有性能方面的缘由,全局对象改变会很频繁,非全局变量的属性访问优化对全局变量来讲并不适用。

  • (roots) - constructor中roots的内容引用它所选中的对象。它们也能够是由引擎自主建立的一些引用。这个引擎有用于引用对象的缓存,可是这些引用不会阻止引用对象被回收,因此它们不是真正的强引用(FIXME)。
  • (roots) – The root entries in the retaining tree view are the entities that have references to the selected object. These can also be references created by the engine for its own purposes. The engine has caches which reference objects, but all such references are weak and won't prevent an object from being collected given that there are no truly strong references.

  • (closure) - 一些函数闭包中的一组对象的引用

  • (array, string, number, regexp) - 一组属性引用了Array,String,Number或正则表达式的对象类型

  • (compiled code) - 简单来讲,全部东西都与compoled code有关。Script像一个函数,但其实对应了<script>的内容。SharedFunctionInfos (SFI)是函数和compiled code之间的对象。函数一般有内容,而SFIS没有(FIXME)。
  • (compiled code) – simply, everything related to compiled code. Script is similar to a function but corresponds to a <script> body. SharedFunctionInfos (SFI) are objects standing between functions and compiled code. Functions are usually have a context, while SFIs do not.

  • HTMLDivElement, HTMLAnchorElement, DocumentFragment 等 - 你代码中对elements或document对象的引用。

在你的程序的生命周期中生成的不少其它的对象,包括事件监听器或自定义对象,能够在下面的controllers中找到:

问:我在作内存分析时须要关闭Chrome里可能会产生影响的什么功能么?

咱们建议在用Chrome DevTools作内存分析时,你可使用全部扩展功能都关闭了的隐身模式,或设置用户文件夹为(--user-data-dir="")后再打开Chrome。

应用,扩展甚至console中的记录都会对你的分析有潜在的影响,若是你想让你的分析可靠的话,禁用这些吧。

写在最后的话

今天的JavaScript引擎已经有很强的自动回收咱们的代码产生的内存垃圾的能力了。就是说,它们只能作到这样了,但咱们的应用仍然被证实了会由于逻辑错误而产生内存泄漏。使用相应的工具来找到你的应用的瓶颈,记住,不要靠猜 - 测试它。

帮助实例

诊断内存泄漏

尽管不少内容在本文章中已经提到了,但一系列测试内存相关的问题的例子仍是颇有用的,下面是一组DOM节点内存泄漏的例子。你可能但愿在测试你的更复杂的页面或应用前先用这些例子作试验。

更多例子:

社区资源

社区贡献了不少如何用Chrome DevTools来定位和解决web apps内存问题的资源。下面的一组资源可能对你有帮助:

相关文章
相关标签/搜索