Posts JS内存分析
Post
Cancel

JS内存分析

使用 Chrome 和 DevTools 查找影响页面性能的内存问题

  • 内存泄漏
  • 内存膨胀
  • 频繁的垃圾回收

内存问题

  • 页面的性能随着时间的延长越来越差。 这可能是内存泄漏的症状。 内存泄漏是指,页面中的错误导致页面随着时间的延长使用的内存越来越多。
  • 页面的性能一直很糟糕。 这可能是内存膨胀的症状。 内存膨胀是指,页面为达到最佳速度而使用的内存比本应使用的内存多。
  • 页面出现延迟或者经常暂停。 这可能是频繁垃圾回收的症状。 垃圾回收是指浏览器收回内存。 浏览器决定何时进行垃圾回收。 回收期间,所有脚本执行都将暂停。因此,如果浏览器经常进行垃圾回收,脚本执行就会被频繁暂停。

监视内存使用

01.png

02.png

  • Memory 列表示原生内存。DOM 节点存储在原生内存中。 如果此值正在增大,则说明正在创建 DOM 节点。
  • JavaScript Memory 列表示 JS 堆。此列包含两个值。 您感兴趣的值是实时数字(括号中的数字)。 实时数字表示您的页面上的可到达对象正在使用的内存量。 如果此数字在增大,要么是正在创建新对象,要么是现有对象正在增长。

使用 Performance 可视化内存泄漏

03.png

一种比较好的做法是使用强制垃圾回收开始和结束记录。 在记录时点击 Collect garbage 按钮可以强制进行垃圾回收。

JS 堆在结束时会比开始时大(这里“开始”是指强制垃圾回收后的时间点)。在实际使用过程中,如果您看到这种 JS 堆大小或节点大小不断增大的模式,则可能存在内存泄漏。

使用堆快照发现已分离 DOM 树的内存泄漏

只有页面的 DOM 树或 JavaScript 代码不再引用 DOM 节点时,DOM 节点才会被作为垃圾进行回收。 如果某个节点已从 DOM 树移除,但某些 JavaScript 仍然引用它,我们称此节点为“已分离”。已分离的 DOM 节点是内存泄漏的常见原因。

打开 DevTools 并转到 Profiles 面板,选择 Take Heap Snapshot 单选按钮,然后按 Take Snapshot 按钮。

在 Class filter 文本框中键入 Detached,搜索已分离的 DOM 树。

04.png

直接占用内存(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 根和控制器你都不需要过多关心。

06.jpg

内存图由一个根部开始,可能是浏览器的 window 对象或 Node.js 模块 Global 对象。这些对象如何被内存回收不受用户的控制。不能被 GC 根遍历到的对象都将被内存回收。直接占用内存和占用总内存字段中的数据是用字节表示的。

查看详情

以黄色突出显示的节点具有 JavaScript 代码对它们的直接引用。 以红色突出显示的节点则没有直接引用。只有属于黄色节点的树时,它们才处于活动状态。 一般而言,您需要将注意力放在黄色节点上。 修复代码,使黄色节点处于活动状态的时间不长于需要的时间,您也需要消除属于黄色节点树的红色节点。

点击黄色节点对其进行进一步调查。在 Object 窗格中,您可以看到与正在引用该节点的代码相关的更多信息。

我们能看到距离(distance)这个字段:是指对象到 GC 根的距离。如果同一个类型的所有对象的距离都一样,而有一小部分的距离却比较大,那么就可能出了些你需要进行调查的问题了。

V8 介绍

JavaScript 对象描述

有三个原始类型:

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

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

数字(Numbers)

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

字符型数据

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

数组(Arrays)

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

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

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

对象组

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

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

使用分配时间线确定 JS 堆内存泄漏

要记录分配时间线,请打开 DevTools,然后转到 Profiles 面板,选择 Record Allocation Timeline 单选按钮,按 Start 按钮,执行您怀疑导致内存泄漏的操作。完成后,按 stop recording 按钮。

05.png

蓝色竖线表示新内存分配。新内存分配中可能存在内存泄漏。 您可以在竖线上放大,将 Constructor 窗格筛选为仅显示在指定时间范围内分配的对象。展开对象并点击它的值,可以在 Object 窗格中查看其更多详情。

This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents

Trending Tags