Chrome 浏览器垃圾回收机制与内存泄漏分析 (2)

使用增量标记算法,可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行上述动画效果时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。

内存泄漏

不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

内存泄漏发生的原因

缓存

有时候为了方便数据的快捷复用,我们会使用缓存,但是缓存必须有一个大小上限才有用。高内存消耗将会导致缓存突破上限,因为缓存内容无法被回收。

队列消费不及时
浏览器队列消费不及时时,会导致一些作用域变量得不到及时的释放,因而导致内存泄漏。

全局变量

除了常规设置了比较大的对象在全局变量中,还可能是意外导致的全局变量,如:

function foo(arg) { bar = "this is a hidden global variable"; }

在函数中,没有使用 var/let/const 定义变量,这样实际上是定义在window上面,变成了window.bar。
再比如由于this导致的全局变量:

function foo() { this.bar = "this is a hidden global variable"; } foo()

这种函数,在window作用域下被调用时,函数里面的this指向了window,执行时实际上为window.bar=xxx,这样也产生了全局变量。

计时器中引用没有清除

先看如下代码:

var someData = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { node.innerHTML = JSON.stringify(someData)); } }, 1000);

这里定义了一个计时器,每隔1s把一些数据写到Node节点里面。但是当这个Node节点被删除后,这里的逻辑其实都不需要了,可是这样写,却导致了计时器里面的回调函数无法被回收,同时,someData里的数据也是无法被回收的。

闭包

看以下这个闭包:

var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) console.log("hi"); }; theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000);

每次调用 replaceThing ,theThing 会创建一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing(theThing) 的闭包,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。

即 someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused 从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。

因此,当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。

本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。

事件监听

例如,Node.js 中 Agent 的 keepAlive 为 true 时,可能造成的内存泄漏。当 Agent keepAlive 为 true 的时候,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听从而产生内存泄漏。

内存泄漏的识别方法

使用 Chrome 任务管理器实时监视内存使用
打开 chrome 浏览器,点击右上角主菜单,选择更多工具->任务管理器,这样就开启了任务管理器面板,然后再右键点击任务管理器的表格标题并启用 JavaScript使用的内存,能看到这样的面板:

下面两列可以告诉您与页面的内存使用有关的不同信息:

GitHub

内存占用空间(Memory) 列表示原生内存。DOM 节点存储在原生内存中。 如果此值正在增大,则说明正在创建 DOM 节点。

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

当你页面稳定下来之后,这两个的值还在上涨,你就可以查一查是否内存泄漏了。

利用chrome 时间轴记录可视化内存泄漏

Performance(时间轴)能够面板直观实时显示JS内存使用情况、节点数量、监听器数量等。

打开 chrome 浏览器,调出调试面板(DevTools),点击Performance选项(低版本是Timeline),勾选Memory复选框。一种比较好的做法是使用强制垃圾回收开始和结束记录。在记录时点击 Collect garbage 按钮 (强制垃圾回收按钮) 可以强制进行垃圾回收。
所以录制顺序可以这样:开始录制前先点击垃圾回收-->点击开始录制-->点击垃圾回收-->点击结束录制。
面板介绍如图:

GitHub


录制结果如图:

GitHub


首先,从图中我们可以看出不同颜色的曲线代表的含义,这里主要关注JS堆内存、节点数量、监听器数量。鼠标移到曲线上,可以在左下角显示具体数据。在实际使用过程中,如果您看到这种 JS 堆大小或节点大小不断增大的模式,则可能存在内存泄漏。

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

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgwxj.html