资讯专栏INFORMATION COLUMN

[JavaScript 随笔] 垃圾回收

ConardLi / 2191人阅读

摘要:在中,由于垃圾回收是自动进行的,所以人们在编码时可能不太会注意这方面。时,引擎统一对所有这些状态的对象进行回收。,表示释放该对象后能得到的内存大小。

在 JavaScript 中,由于垃圾回收是自动进行的,所以人们在编码时可能不太会注意这方面。但事实是,一些 webapp 在使用一段时间后,会出现卡顿的现象,特别是那些单页应用,包括 WebView 方式的手机 app 。这个现象在传统的“单击 - 刷新”类型的页面中并不明显,因为页面刷新之后,所有没有被回收的垃圾对象也会被清除,但是在单页应用中,如果没有手动去点浏览器的刷新按钮,那么就算是很小的内存泄露,随着页面停留时间的增长,累积的泄露会越来越多,在手机上的感觉就更明显了。

所以这里想讨论一下内存泄露是如何发生的,以及如何去避免。

开门见山,一般有两种方式的垃圾回收机制,一个是“引用计数”,当一个对象被引用的次数为 0 时,该对象就可以被回收;另一个是“标记清除”,当一个对象不能再被访问到时,对该对象进行标记,等下一轮 GC 事件发生时,这些对象就会被清除。从 2012 年起,所有的现代浏览器都是基于“标记清除”的回收算法,所以,如果需要兼容更早的浏览器,可能需要做更多的事。GC 的时机由 JS 引擎决定,需要知道的事,当 GC 进行时,主线程会被阻塞,这个时间可以通过 Chrome 的 Timeline 工具看到,最少也会超过 10 ms 吧。

Chrome DevTools - Timeline

在 Chrome 中可以很直观方便地看到垃圾回收事件的执行。打开 Chrome 的 Timeline,只需要勾选“Memory”就可以了,并且在左边的 View 中选中第二个。

然后单击放大镜下面的圆点,这时候 Chrome 会开始记录内存分配、绘制等事件,等你打开一张页面,比如百度吧,再单击这个圆点(现在应该是红色的了),就会看到一条蓝色的折线。不同页面不一样,但几乎都会有一个突然下降的地方,比如下图中 1200 ms 左边的地方,单击它,就能在下方显示 GC 事件所用的时间,以及它回收了多少内存。

如果你看到自己网站的这条蓝色折线是呈上升趋势,在不断的 GC 后,内存还是在上升,就极有可能是发生了内存泄露,需要排查一下代码。

引用计数

这里的问题在于“循环引用”,如果对象 a 的属性引用了 b,而 b 的属性引用了 a,由于引擎只有在变量的引用次数为 0 的情况下才会回收,这里 a 和 b 的引用次数至少有 1,所以就算它们所在的函数执行完了,这两个变量还是无法被回收掉。

function foo() {
  var a = {},
      b = {};
      
  a.attr = b;
  b.attr = a;
}

foo();

当 foo 函数执行很多次之后,就会有很多个无法被回收的 a 和 b 存在。

实际情况可能是这样的:

function foo() {
  var text = document.getElementById("input-text");

  text.onfocus = function() {
    text.value = "";
  }
}

foo();

意思是,当光标移到输入框时,清空原有的内容。考虑 text 变量和 foo 里面的匿名函数,text 的 onfocus 属性引用了匿名函数,而该匿名函数引用了 text 变量(循环了),所以当 foo 执行结束后,这两个对象由于引用次数大于 0 而无法被回收。

对于这种情况,只需要在 foo 的末尾对 text 变量置空就可以了。

text = null;

如果你用 Chrome 运行这个例子的话,会看到蓝线还是降到初始的高度了,因为 Chrome 是基于“标记清除”的算法来回收内存的,所以不会有“循环引用”的问题。

标记清除

对于标记清除,心中要想象一个树,每个页面都存在一个根,每当一个函数执行,就会生成一个节点。自然,嵌套的函数调用就会有子节点。一般情况下(没有闭包),当函数执行完时,内部的变量都是无法被其他代码访问的,所以它就被标记为“无法被访问”。GC 时,JS 引擎统一对所有这些状态的对象进行回收。

介绍两个概念。Shallow Size,表示该对象本身占用的内存。Retained Size,表示释放该对象后能得到的内存大小。什么意思?比如上图绿色的 #3,这个绿色的面积就是 Shallow Size。释放 #3 后,#4 和 #5 也会被释放,所以 Retained Size 就是 #3、#4、#5 的总大小。

在“标记清除”算法中,难点是如何判断一个对象已经是“无法被访问”了。

DOM 片段

如果用树去分析垃圾回收,会发现其实我们需要做的事情很少,因为当一个函数执行完之后,它连带的对象都会被清除。就算有闭包,当引用该闭包的函数执行完时,这些闭包也同样会被标记。

那么在哪里会发生内存泄露呢?看这个例子。

var btn = document.getElementById("btn");

btn.onclick = function() {
  var fragment = document.createElement("div");
}

它表示每单击一次按钮,就创建一个

,它没有引用任何对象,但是回调结束之后,这个空的
是不会被回收的。

DOM 事件
var content = document.getElementById("content");
content.innerHTML = "";

var button = document.getElementById("button");
button.addEventListener("click", function() {});

content.innerHTML = "";

这段代码过后,虽然

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85932.html

相关文章

  • JavaScript权威指南》随笔(一)

    摘要:每个构造函数定义了一类对象,表示由构造函数初始化对象的集合。严格模式下,明确禁止八进制数。日期和时间构造函数用来创建表示日期和时间的对象,包含方法。模式匹配函数是一个构造函数,创建正则表达式。布尔值表示两种状态,使用保留字和。 《Javascript权威指南》就是前端工程师口中常说的犀牛书,得名是因为中文翻译出版的书籍封面是一只犀牛,是学习JavaScript的必读书籍。 JavaSc...

    SwordFly 评论0 收藏0
  • JavaScript深入浅出第3课:什么是垃圾回收算法?

    摘要:摘要是如何回收内存的深入浅出系列深入浅出第课箭头函数中的究竟是什么鬼深入浅出第课函数是一等公民是什么意思呢深入浅出第课什么是垃圾回收算法最近垃圾回收这个话题非常火,大家不能随随便便的扔垃圾了,还得先分类,这样方便对垃圾进行回收再利用。 摘要: JS是如何回收内存的? 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? Jav...

    AaronYuan 评论0 收藏0
  • javascript 垃圾回收算法

    摘要:它将堆内存一分为二每一部分空间称为。以的垃圾回收堆内存为例做一次小的垃圾回收需要毫秒以上做一次非增量式的垃圾回收甚至要秒以上。这是垃圾回收中引起线程暂停执行的时间在这样的时间花销下应用的性能和响应能力都会直线下降。 我们通常理解的 javascript 垃圾回收机制都停留在表面,会释放不被引用变量内存,最近在读《深入浅出node.js》的书,详细了解了下 v8 垃圾回收的算法,记录了一...

    simon_chen 评论0 收藏0
  • JavaScript 闯关记》之垃圾回收和内存管理

    摘要:内存回收此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用。局部变量会在它们离开执行环境时自动被解除引用,如下面这个例子所示手工解除的引用由于局部变量在函数执行完毕后就离开了其执行环境,因此无需我们显式地去为它解除引用。 JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。而...

    Sleepy 评论0 收藏0
  • JavaScript中的垃圾回收和内存泄漏

    摘要:所谓的内存泄漏简单来说是不再用到的内存,没有及时释放。如果一个值不再需要了,引用数却不为,垃圾回收机制无法释放这块内存,从而导致内存泄漏。 前言 程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存。所谓的内存泄漏简单来说是不再用到的内存,没有及时释放。为了更好避免内存泄漏,我们先介绍Javascript垃圾回收机制。 在C与C++等语言中,开发人员可以直接控制内存的...

    microelec 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<