JavaScript setInterval()方法会导致内存泄漏吗?

目前正在开发基于JavaScript的animation项目。

我注意到,正确使用setInterval()setTimeout()甚至requestAnimationFrame分配内存没有我的请求,并导致频繁的垃圾收集调用。 更多的GC调用=闪烁:-(

例如; 当我通过在Google Chrome中调用init()来执行下面的简单代码时 ,内存分配+垃圾回收在第一个20-30秒内是正确的…

 function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { return true } 

不知何故,在一分钟左右,分配内存开始奇怪的增加! 由于init()只被调用一次, 分配内存大小增加的原因是什么?

(编辑:chrome截图上传)

铬的截图

注#1:是的,我已经尝试在下一个setInterval()之前调用clearInterval()。 问题依旧!

注2:为了隔离问题,我保持上面的代码简单和愚蠢。

编辑: 尤里的答案是更好的。


tl;医生国际海事组织没有内存泄漏。 正斜率只是setInterval和setTimeout的效果。 垃圾被收集,如锯齿图案所示,意思是没有内存泄漏。 (我认为)。

我不确定是否有办法解决这个所谓的“内存泄漏”问题。 在这种情况下,“内存泄漏”指的是每次调用setInterval函数都会增加内存使用量,如内存分析器中的正斜率所示。

实际情况是没有实际的内存泄漏:垃圾收集器仍然能够收集内存。 内存泄漏的定义“发生在计算机程序获取内存但未能将其释放回操作系统。

如下面的内存configuration文件所示,不会发生内存泄漏。 内存使用量随着每个函数调用而增加。 操作系统预计,因为这是一遍又一遍被调用的相同函数,应该没有增加内存。 然而,这种情况并非如此。 每个函数调用都会消耗内存。 最终,垃圾被收集起来,形成锯齿图案。

我已经探索了重新排列间隔的几种方法,它们都导致相同的锯齿模式(尽pipe有些尝试导致垃圾收集永远不会发生,因为引用被保留)。

 function doIt() { console.log("hai") } function a() { doIt(); setTimeout(b, 50); } function b() { doIt(); setTimeout(a, 50); } a(); 

http://fiddle.jshell.net/QNRSK/14/

 function b() { var a = setInterval(function() { console.log("Hello"); clearInterval(a); b(); }, 50); } b(); 

http://fiddle.jshell.net/QNRSK/17/

 function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { console.log('Hello'); } init(); 

http://fiddle.jshell.net/QNRSK/20/

 function init() { window.ref = window.setInterval(function() { draw(); }, 50); } function draw() { console.log('Hello'); clearInterval(window.ref); init(); } init();​ 

http://fiddle.jshell.net/QNRSK/21/

显然setTimeoutsetInterval不是正式的Javascript部分(因此它们不是v8的一部分)。 这个实现是由实施者决定的。 我build议你看一下在Node.js中执行setInterval等

这里的问题不在代码本身,它不泄漏。 这是因为时间轴面板的实现方式。 当时间轴logging事件时,我们会在每次调用setIntervalcallback时收集JavaScript堆栈跟踪。 堆栈跟踪首先在JS堆中分配,然后复制到本地数据结构中,堆栈跟踪被复制到本地事件后,它成为JS堆中的垃圾。 这反映在图表上。 禁用以下调用http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/TimelineRecordFactory.cpp#L55使内存图平坦。;

存在与此问题相关的错误: https : //code.google.com/p/chromium/issues/detail?id=120186

每次进行函数调用时,都会创build一个堆栈帧 。 与许多其他语言不同,Javascript像堆栈一样将堆栈框架存储在堆上。 这意味着每当你调用一个你每50ms执行一次的函数,一个新的堆栈框架就被添加到堆中。 这加起来,并最终收集垃圾。

鉴于Javascript的工作原理,这是不可避免的。 唯一可以减轻负担的方法就是尽可能减小堆栈帧,我相信所有的实现都是这样做的。

我想回应你对setInterval和flickering的评论:

我注意到,正确使用setInterval(),setTimeout()甚至requestAnimationFrame分配内存没有我的请求,并导致频繁的垃圾收集调用。 更多GC调用=闪烁:-(

您可能想要尝试使用基于setTimeout的较less的自调用函数replacesetInterval调用。 保罗·爱尔兰在谈话中提到了这件事,我从jQuery的源头学到了10个东西(video在这里 ,注意这里见#2)。 你所做的就是把你的调用replace为一个函数, 它在完成它应该做的工作之后,通过setTimeout间接调用 。 引用这个话题:

许多人认为setInterval是一个邪恶的function。 它不断地以特定的时间间隔调用函数,而不pipe函数是否完成。

使用上面的示例代码,您可以更新您的init函数:

 function init() { var ref = window.setInterval(function() { draw(); }, 50); } 

至:

 function init() { //init stuff //awesome code //start rendering drawLoop(); } function drawLoop() { //do work draw(); //queue more work setTimeout(drawLoop, 50); } 

这应该有点帮助,因为:

  1. 绘制()将不会被渲染循环再次调用,直到完成
  2. 正如上面的许多回答指出的那样,所有来自setInterval的不间断函数调用都会在浏览器上开销。
  3. debugging更容易,因为你不会被setInterval的持续触发中断

希望这可以帮助!

Chrome几乎看不到任何程序的内存压力(1.23 MB是现在的标准,内存使用量非常低),所以它可能不会认为它需要积极的GC。 如果你修改你的程序使用更多的内存,你会看到垃圾收集器踢,例如试试这个:

 <!html> <html> <head> <title>Where goes memory?</title> </head> <body> Greetings! <script> function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { var ar = new Array(); for (var i = 0; i < 1e6; ++i) { ar.push(Math.rand()); } return true } init(); </script> </body> </html> 

当我运行这个时,我得到了一个锯齿内存使用模式,峰值波动约13.5MB(再次,今天的标准相当小)。

PS:我的浏览器的细节:

 Google Chrome 23.0.1271.101 (Official Build 172594) OS Mac OS X WebKit 537.11 (@136278) JavaScript V8 3.13.7.5 Flash 11.5.31.5 User Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11 

尝试没有匿名函数做这个。 例如:

 function draw() { return true; } function init() { var ref = window.setInterval(draw, 50); } 

它仍然performance相同的方式?

似乎没有内存泄漏。 只要内存使用量在GC之后再次下降,整体内存使用量平均没有上升趋势,就没有泄漏。

我在这里看到的“真正的”问题是, setInterval确实使用内存来操作,而且看起来不应该分配任何东西。 实际上它需要分配一些东西:

  1. 它将需要分配一些堆栈空间来执行匿名函数和draw()例程。
  2. 我不知道是否需要分配任何临时数据来执行自己的电话(可能不是)
  3. 它需要分配less量的存储来保存来自draw() true返回值。
  4. 在内部,setInterval可能会分配额外的内存来重新计划重复发生的事件(我不知道它是如何工作的,它可能会重新使用现有的logging)。
  5. JIT可能会尝试跟踪该方法,这将为跟踪和一些度量标准分配额外的存储空间。 虚拟机可能会确定这个方法太小,无法跟踪它,我不知道所有的阈值打开或closures跟踪。 如果你运行这段代码的时间足够让VM识别出它是“hot”,那么它可能会分配更多的内存来保存JIT编译的机器代码(在这之后,我预计平均内存使用量会减less,因为生成的机器代码应该在大多数情况下分配更less的内存)

每次你的匿名函数执行的时候都会分配一些内存。 当这些分配加起来达到某个门槛的时候,GC就会启动并清理,让你回到基本的水平。 循环将继续如此直到你closures它。 这是预期的行为。

我也有同样的问题。 客户告诉我,它的电脑内存越来越多。 起初,我认为一个networking应用程序即使被一个简单的浏览器访问,也是非常奇怪的。 我注意到这只发生在Chrome中。

然而,我开始与合作伙伴进行调查,并通过Chrome的开发人员工具和经理任务,我们可以看到客户向我报告的内存增加。

然后我们看到,一个jQuery的function(请求animation帧)不断增加系统内存加载。 之后,我们看到了这个post的感谢,jquery倒计时是这样做的,因为它里面有一个“SETINTERVAL”,每次更新我的应用程序布局中的date。

当我正在使用ASP.NET MVC时,我刚刚从BundleConfig退出了这个jquery脚本倒计时,并从我的布局中用下面的代码replace了我的倒计时:

 @(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))