Skip to content

Instantly share code, notes, and snippets.

@Misaka-0x447f
Last active May 19, 2018 06:13
Show Gist options
  • Save Misaka-0x447f/dedde88ad872f4d7779024fbc9118a4f to your computer and use it in GitHub Desktop.
Save Misaka-0x447f/dedde88ad872f4d7779024fbc9118a4f to your computer and use it in GitHub Desktop.
翻译:Fix memory problems

修正内存问题

原始作者: Kayce Basques 译者:Misaka

这篇文章可以帮助你学习如何使用Chrome及其开发者工具来找到影响页面性能的、包括内存泄露、内存膨胀和过于频繁的gc(垃圾收集)等问题。

太长不看

  • 使用Chrome任务管理器找出你的页面当前使用了多少内存。
  • 使用时间线(Timeline)记录工具使内存使用可视化。
  • 使用内存堆快照(Heap Snapshots)定位孤立的DOM树(内存泄漏的通常成因)。
  • 使用分配时间线记录(Allocation Timeline recordings)工具找到新的内存即将被分配到你的JS内存堆的时刻。

总览

RAIL性能模型的精神中,性能的焦点应当是你的用户。
因为经常可以被用户察觉到,内存问题很重要。如果愿意,用户可以通过以下方式察觉内存问题:

  • 页面的性能随着时间流逝而逐渐变差。这可能是内存泄漏的症状。内存泄漏是一种随着时间流逝,页面逐渐使用越来越多的内存的bug。
  • 页面的性能一直很差。这可能是内存膨胀的症状。内存膨胀指页面使用了超过其所需的、对于优化页面速度所必须的内存。
  • 页面响应缓慢或卡顿。这可能是过于频繁地进行gc(Garbage collection,垃圾收集)的症状。gc是浏览器收回内存的动作,浏览器决定它何时发生。在gc期间,所有脚本的执行都将被暂停。所以如果浏览器不停地gc,脚本执行也会不时地暂停。

内存膨胀:用多少才太多?

内存泄漏是很容易定义的:如果一个页面不断地占用越来越多的内存,那肯定发生了内存泄漏。但是要定义内存膨胀可不容易,什么情况下才是用了太多内存呢?
这里没有硬性指标,因为不同的设备和浏览器性能也不同。同一个页面也许在高端的手机上丝般顺滑,但在低端手机上就会崩溃。
这里的关键是使用RAIL模型并且专注于你的用户。确定你的目标用户主要使用什么设备,然后在这些设备上做测试,如果体验经常性地很差,这个页面也许就使用了超过这些设备能提供的内存总量。

使用Chrome任务管理器实时监视内存的使用量

使用Chrome任务管理器作为你内存问题调查的起点。Chrome任务管理器是一个能告诉你某个页面当前使用的内存量的实时监视器。

  1. Shift+Esc或者点Chrome主选单,然后选择更多工具>任务管理器来打开Chrome任务管理器。 打开Chrome任务管理器
  2. 右键点击任务管理器的表头,并启用JavaScript 内存Chrome任务管理器 这两栏将告诉你有关你的页面如何使用内存的不同的信息:
  • 内存栏显示原生内存使用量。DOM节点被储存在原生内存中,这个值不断增长意味着DOM节点正在不断被创建。
  • Javascript 内存栏显示JS内存堆。这一栏有两个数值,你可能对live数值(括号中的)感兴趣,这是在你的页面上可以到达的object使用的内存的总量。如果你发现这个数值在不断增长,可能是新的object正在被创建,也可能是已经存在的object正在增长。

使用时间线记录工具可视化内存泄漏

你也可以使用"时间线"面板(译者注:现在改名叫Performance了,下文都会写Performance)作为另一个调查的起点。时间线面板可以帮助你可视化一个页面的内存使用量。

  1. 在开发者工具中打开**Performance(性能)**面板
  2. 选中**Memory(内存)**复选框
  3. 进行一次录制。 提示:在录制的开始和结束进行一次强制gc是一个很好的实践。当录制时,点击Collect garbage按钮(collect garbage)来进行强制gc。
    使用下面的代码来证明时间线内存录制功能:
var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

代码中引用的按钮每次被按下,都会把一万个div节点附加到document.body,并向数组x添加一百万个x字符。运行这个代码的结果会像是这样: Timeline 我们先解释一下这个用户界面。总览(Overview)面板中的HEAP(堆)图表(在NET(网络))下)显示JS内存堆的占用情况。在Overview面板下方的是计数器面板。在这里,你可以看到内存使用显示为JS Heap(和Overview面板中的HEAP图表一样)、documents(文档)、DOM nodes(DOM节点)、listeners(监听器)和GPU Memory(GPU内存)。取消勾选一个复选框可以从图表中将其隐藏。
现在,一个代码分析将和屏幕截图一起显示。现在,注意节点数(绿色的线),你可以发现它不连续地增长,和我们的代码完全吻合。我们可以推测每次调用grow()都增加了节点数。JS heap(蓝色的线)则不那么容易理解。事实上,第一个下降的原因是一次强制gc(通过点击collect garbage按钮)。随着录制进程的进行,你可以看到JS内存堆有一次突起,这是预料之中的:每次点击按钮时,JavaScript代码会创建DOM节点,并在创建长度为一百万的字符串的同时做很多其他的事。这里的最关键的事是,事实上JS内存堆结束的时候比开始更高(在这里,“开始”指强制gc之后)。在实践中,如果你注意到这个JS heap体积或node体积增长的模式,它可能意味着发生了一次内存泄漏。

用Heap快照发现孤立的DOM树内存泄漏

一个DOM节点只能在没有被页面的DOM树和JavaScript引用时被gc。孤立的节点的意思是,它被从DOM树中移除,但仍有JavaScript在引用它。孤立的DOM节点是导致内存泄漏的常见原因。这一节将教你如何使用开发者工具的Heap Profiler来识别孤立节点。

这里有一个孤立节点的简单例子:

var detachedNodes;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedNodes = ul;
}

document.getElementById('create').addEventListener('click', create);

点击代码中引用的按钮将创建一个有10个li子节点的ul节点。这些节点被代码引用,但不存在于DOM树中,所以它们是孤立的。
Heap快照是一种识别孤立节点的办法。顾名思义,Heap快照所显示的是这一时间点的内存在JS对象和DOM节点上的分配。
要创建一个快照,在开发者工具中选择Profiles(译者注:改名Memory),选择Heap Snapshot,然后点击Take Snapshot. Memory profile 快照可能需要一些时间来分析和加载。当完成后,从左侧面板选择它。
Class filter文本框中输入Detached来搜索孤立的DOM树。
Snapshot 1 展开这些项目来分析孤立树。
Detached DOM tree 被JavaScript直接引用的节点标注为黄色高亮。没有直接引用的节点标注为红色高亮,因为它们是黄色节点树的一部分,所以它们还没有被回收。总的来说,你应该注意黄色节点。修复你的代码,以便在不再需要黄色节点时移除它,然后你就能同时移除作为黄色节点的一部分的红色节点。
点击黄色节点以便继续研究它。在Object面板你可以看到引用它的代码的更多信息。例如,在下面的屏幕截图中你可以看到变量detachedTree正在引用这个节点。要修复这部分的内存泄漏,你也许要看一下使用detachedTree的代码,然后确保当不再使用的时候移除它。 Object

使用Allocation Timelines功能识别内存堆泄漏

Allocation Timeline(改名Allocation instrumentation on timeline)是另一个可以帮助你跟踪内存堆中的内存泄漏的工具。
我们使用以下代码为例:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

每次按下按钮,1M个'x'字符串被加入变量x。 要记录一个内存分配时间轴,打开开发者工具,选择Memory面板,选择Allocation instrumentation on timeline,按Start,执行你怀疑导致内存泄漏的动作,当你完成时按Stop recording按钮(stop)。
当你录制时,请注意Allocation Timeline上出现的蓝条,就像下面的截图中的: Timelines 这些蓝条代表新的内存分配。这些新的内存分配是可能存在的内存泄漏。你可以缩放到一个蓝条来过滤构造器面板以便显示在指定时间帧分配的对象。
Timelines zoomed 展开对象,然后点击它的值来在Object面板查看关于它的更多详情。例如,在下面的截图中,通过查看新分配的对象,你可以看到它在Window作用域中被分配给x变量。 Memory object details

按函数显示内存分配

使用Record Allocation Profiler(改为Allocation sampling)来按JavaScript function查看内存分配。
Record Allocation Profiler

  1. 选择Allocation sampling。如果这个页面上有worker,你可以将其作为性能诊断对象。
  2. Start
  3. 执行你想分析的动作。
  4. 完成后按Stop。 开发者工具将显示一个当前页面的内存分配,默认排序是按最多使用量排序。
    allocation profile

发现频繁的垃圾收集事件

如果你的页面看起来很卡,也许你遇到了垃圾收集的问题。
你可以用Chrome任务管理器或Performance内存录制工具来发现频繁的垃圾收集事件。在任务管理器中,MemoryJavaScript Memory频繁地增加和减少可能意味着发生了频繁的垃圾收集。Performance那边也是一样。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment