JavaScript 随笔之垃圾回收的例子

在 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();

实际情况可能是这样的:

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');

}

它表示每单击一次按钮,就创建一个 <div>,它没有引用任何对象,但是回调结束之后,这个空的 <div> 是不会被回收的。

DOM 事件

var content = document.getElementById('content');

content.innerHTML = '<button id="button">click</button>';var button = document.getElementById('button');

button.addEventListener('click', function() {});

content.innerHTML = '';

这段代码过后,虽然 <button> 从 DOM 中移除了,由于它的监听器还在,所以无法被 GC 回收。

要避免这种情况就是通过 removeEventListener 将回调函数去掉。

定时器

如果使用 setInterval,那么它引用到的变量的上下文会保留下来。

function foo() {  var name  = 'tom',

      title = 'Hero';      

  window.setInterval(function() {

    alert(name);

  }, 1000);

}

foo();

另一方面,你不用为了仅仅避免内存泄露对 setTimeout 调用 clearTimeout 。它是不会造成内存泄露的,除非是别的什么原因,比如说,在 setTimeout 中递归调用了当前定时器,这相当于模拟 setInterval,可以与 setInterval 做类似处理。

 

小结

 

在平时的一些开发过程中,我发现虽然在 Chrome 中发生了 GC 事件,并且内存也降得很低,如果用 Profile 工具 Take Heap Snapshot 的话,也不会觉得有内存泄露发生。但在手机上(WebView)的确会存在越用越卡的现象,这点可能要根据不同的环境来分析,但文中提到的关键两个地方就是:解除引用,以及解除监听的事件。

 

如果自己的代码中能做到这两点的话,可能卡顿是由别的问题引起的,而不是内存泄露

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索内存
, 对象
, 函数
, 变量
, 事件
闭包
javascript垃圾回收、垃圾回收例子、幼儿园家长随笔例子、javascript例子、javascript小例子,以便于您获取更多的相关知识。

时间: 2024-08-02 04:41:39

JavaScript 随笔之垃圾回收的例子的相关文章

JavaScript具有自动垃圾回收机制

JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存.   正常的生命周期:     局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或堆内存上分配响应的空间,以便存储他们的值. 然后在函数中使用这些变量,直至这些函数执行结束.   JavaScript最常用的垃圾收集方式是:标记清除. 当变量进入环境时,标记为"进入环境". 离开环境,标记为"离开环境".   一旦数据不再使用,最好通过将其值设为nu

javascript中的垃圾回收

  1引用计数垃圾回收 核心:跟踪记录对象被引用的次数.思路是如果一个对象A被赋值给了一个变量v,则该对象A的引用计数值加1,如果变量v又被赋予其他值了,比如a="str",则该对象A的引用计数值减1.当这个引用计数值变成0时,就表明它所占的内存空间可以被回收了. 1 var A={b:4}; 2 var v=A;//此时A的引用计数值为1 3 var vv=A; //此时A的引用计数值为2 4 v=9;//A的引用计数值为1 5 vv="hah";//A的引用计数

JavaScript 垃圾回收机制分析_javascript技巧

在公司经常会听到大牛们讨论时说道内存泄露神马的,每每都惊羡不已,最近精力主要用在了Web 开发上,读了一下<JavaScript高级程序设计>(书名很唬人,实际作者写的特别好,由浅入深)了解了一下JavaScript垃圾回收机制,对内存泄露有了一定的认识. 和C#.Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在开发过程中就无需考虑内存分配及无用内存的回收问题了.JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其

浅谈JavaScript 执行环境、作用域及垃圾回收_javascript技巧

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为.每个执行环境都有一个与之关联的变量对象. 全局执行环境是最外围的一个执行环境.根据JavaScript实现所在的宿主环境不同,表示执行环境的对象也不一样.在Web浏览器中,全局执行环境被认为是window对象.因此,所有的全局变量和函数都是作为window对象的属性和方法创建的. 变量对象:环境中定义的所有变量和函数都保存在这个对象中. 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链.作用域链的用途是保证对执行环

javascript垃圾回收浅析

1引用计数垃圾回收 核心:跟踪记录对象被引用的次数.思路是如果一个对象A被赋值给了一个变量v,则该对象A的引用计数值加1,如果变量v又被赋予其他值了,比如a="str",则该对象A的引用计数值减1.当这个引用计数值变成0时,就表明它所占的内存空间可以被回收了. 1 var  A={b:4}; 2 var v=A;//此时A的引用计数值为1 3 var vv=A; //此时A的引用计数值为2 4 v=9;//A的引用计数值为1 5 vv="hah";//A的引用计数值

JS闭包、作用域链、垃圾回收、内存泄露相关知识小结_javascript技巧

补充: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 闭包的特性 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包的定义及其优缺点 闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露. 闭包是javascript

析JAVA之垃圾回收机制

本文为2010年编写,所以有很多看法不是很准确,有一定的参考价值,如需要更加深入细节,请参看,2012年编写的关于JVM的文章: 认识JVM--第一篇-对象生成&回收算法 认识JVM--第二篇-java对象内存模型 JVM第三篇(简单demo) 系统架构-性能篇章1(应用系统性能2-OOM&参数配置) 相继的还会有更多的java深入的知识和机制. 对于JAVA编程和很多类似C.C++语言有一个巨大区别就是内存不需要自己去free或者delete,而是由JVM垃圾回收机制去完成的.对于这个过

.Net托管资源非托管资源垃圾回收的疑问

CLR为开发者提供了一个非常让人激动的功能--垃圾回收.但是园子里关于垃圾回收的讨论,大多是讨论垃圾回收的原理,以及Dispose模式.但是垃圾回收在实际使用时,是不是可以达到其设计的目标,在开发过程中有没有需要注意的问题呢?本人也不是非常明确,这篇文章希望能达到抛砖引玉的效果,希望个人牛人能够给本人或同样存在疑惑的人一个清楚明确的答案. 什么是垃圾回收?就是说你在使用CLR的时候(不包含托管资源) ,只需要new一个对象使用.而不需要通过程序代码进行释放对象(以上是本人理解的垃圾回收的意义).

垃圾回收时发生的一个诡异问题

前些天在论坛里看到了一篇帖子垃圾收集问题--是不是bug其问题如下: static tc gto; public class tc { public int a=99; ~tc() { a=-1; //set breakpoint 1 gto=this; } } private void button1_Click(object sender,EventArgs e) { tc to=new tc(); GC.Collect(); GC.WaitForPendingFinalizers(); r