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

Posted

技术标签:

【中文标题】JavaScript setInterval() 方法会导致内存泄漏吗?【英文标题】:Does JavaScript setInterval() method cause memory leak? 【发布时间】:2012-12-11 15:05:54 【问题描述】:

目前正在开发一个基于 javascript 的动画项目。

我注意到,正确使用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:为了隔离问题,我保持上面的代码简单而愚蠢。

【问题讨论】:

你如何在 chrome 中检查“内存分配 + 垃圾回收”? @chovy 设置->工具->任务管理器?但这并没有显示垃圾收集。 开发者工具 > 时间线 > 内存 > 记录 我很想知道如果你刚刚做了setInterval(draw, 50); 是否会遇到问题,也许与匿名函数的紧密间隔和范围建立/拆除有关?我本来以为 Chrome 会缓存该功能。 在 chrome 23 和 26 上确认 【参考方案1】:

编辑:Yury's answer 更好。


tl;dr IMO 没有内存泄漏。正斜率只是 setInterval 和 setTimeout 的效果。垃圾被收集,如锯齿模式所见,这意味着根据定义没有内存泄漏。 (我认为)。

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

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

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

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

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 的一部分)。实施留给实施者。我建议你看看the implementation of setInterval and such in node.js

【讨论】:

是的,正如我在原始消息中发布的那样,我已经知道垃圾回收了。有用。我无法理解的是,为什么单个计时器方法(例如 setInterval())一直在消耗内存?从您的 JSFiddle 中,我可以看到从 1.1 分钟到 4.8 分钟,内存分配增加(不断增加)!它请求更多内存 = 触发更多 GC 调用!为了停止不必要的 GC 调用,需要“驯服” setInterval() 以便它停止分配内存... @user1928710 我的错。我现在正在研究正确的问题。我的猜测是函数调用不断被推入堆栈。 我怀疑它是堆栈,因为您在这里处理的不是递归,而是异步操作。 @inspectahdeck 我认为你是对的,这只是给定时间间隔内每个函数调用所需的常规内存分配。 希望我们可以从 chromium 论坛 (code.google.com/p/chromium/issues/detail?id=167647) 获得 官方 答案,并通过计时器方法了解它是否只是 (ir) 常规内存分配请求。 .【参考方案2】:

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

【讨论】:

他的问题是“为什么我的简单程序一直在分配内存,导致垃圾回收” 是的,垃圾回收没问题。也许我应该改写我的问题:为什么一个简单的计时器方法会分配这么多内存?如果我们能弄清楚这一点,我们就可以找到一种编写垃圾收集友好代码的方法,并且只需最少的 GC 调用。无闪烁动画所必需的...... 调用函数需要内存。我没有提到这一点,因为我认为这是显而易见的。 matahari,不要在意这个。 “过早的优化是万恶之源”:en.wikiquote.org/wiki/… 这不是过早的优化,他在做动画,当您在动画循环期间浪费分配内存时,您不仅可能会损害帧速率,而且主要问题是当垃圾收集器启动(垃圾收集器运行时用户代码不会执行 - 因此您的动画会短暂停止)。【参考方案3】:

尝试在不使用匿名函数的情况下执行此操作。例如:

function draw()

    return true;


function init()

    var ref = window.setInterval(draw, 50);

它的行为还是一样吗?

【讨论】:

这行不通,不是吗?您不需要将函数传递给 setInterval 而不是返回值吗? 他没有执行draw,他正在传递引用。 draw后面没有()只是一个参考。【参考方案4】:

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

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

    它需要分配一些堆栈空间来执行匿名函数和 draw() 例程。 我不知道是否需要分配任何临时数据来自己执行调用(可能不需要) 它需要分配少量存储空间来保存来自draw()true 返回值。 在内部,setInterval 可能会分配额外的内存来重新安排重复发生的事件(我不知道它在内部是如何工作的,它可能会重复使用现有的记录)。 JIT 可能会尝试跟踪该方法,这将为跟踪和一些指标分配额外的存储空间。 VM 可能会确定此方法太小而无法跟踪它,我不确切知道打开或关闭跟踪的所有阈值是多少。如果您运行此代码足够长的时间以使 VM 将其识别为“热”,它可能会分配更多内存来保存 JIT 编译的机器代码(之后,我预计平均内存使用量会减少,因为生成的机器代码应该在大多数情况下分配更少的内存)

每次执行匿名函数时都会分配一些内存。当这些分配加起来达到某个阈值时,GC 将启动并清理以使您回到基本级别。循环将继续如此,直到您将其关闭。这是预期的行为。

【讨论】:

值得注意的是,Javascript 在将堆栈帧放在堆上时有点不寻常。 还需要分配arguments对象。【参考方案5】:

每次调用函数时,都会创建一个stack frame。与许多其他语言不同,Javascript 将堆栈帧存储在堆上,就像其他所有语言一样。这意味着每次调用一个函数(每 50 毫秒执行一次)时,都会将一个新的堆栈帧添加到堆中。这加起来并最终被垃圾收集。

考虑到 Javascript 的工作原理,这有点不可避免。唯一可以真正减轻它的方法是使堆栈帧尽可能小,我相信所有的实现都会这样做。

【讨论】:

【参考方案6】:

我想回复您关于 setInterval 和闪烁的评论:

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

您可能想尝试用基于 setTimeout 的 less evil 自调用函数替换 setInterval 调用。 Paul Irish 在名为“我从 jQuery 源代码中学到的 10 件事”的演讲中提到了这一点(视频 here,注释 here 参见 #2)。您所做的是将您对 setInterval 的调用替换为一个函数,该函数在 完成它应该做的工作后通过 setTimeout 间接调用自身。引用演讲:

许多人认为 setInterval 是一个邪恶的函数。无论函数是否完成,它都会以指定的时间间隔不断调用函数。

使用上面的示例代码,您可以从以下位置更新您的 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);

这应该会有所帮助,因为:

    draw() 在完成之前不会被渲染循环再次调用 正如上述许多答案所指出的那样,来自 setInterval 的所有不间断函数调用都会增加浏览器的开销。 调试更容易一些,因为您不会被 setInterval 的持续触发所打断

希望这会有所帮助!

【讨论】:

【参考方案7】:

这里的问题不在于代码本身,它没有泄漏。这是因为时间轴面板的实现方式。当 Timeline 记录事件时,我们会在每次调用 setInterval 回调时收集 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

【讨论】:

【参考方案8】:

我也有同样的问题。客户向我反映,其计算机的内存每次都在增加。起初我觉得一个网络应用程序可以做到这一点真的很奇怪,即使它是通过一个简单的浏览器访问的。我注意到这只发生在 Chrome 中。

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

然后我们看到一个 jquery 函数(请求动画帧)被一遍又一遍地加载,增加了系统内存。在那之后,感谢这篇文章,我们看到了一个 jquery 倒计时,因为它有一个“SETINTERVAL”,每次都在更新我的应用布局中的日期。

当我使用 ASP.NET MVC 时,我只是从 BundleConfig 和我的布局中退出了这个 jquery 脚本倒计时,用以下代码替换我的时间倒计时:

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

【讨论】:

以上是关于JavaScript setInterval() 方法会导致内存泄漏吗?的主要内容,如果未能解决你的问题,请参考以下文章

Javascript:setInterval'Resumes'而不是'Reset'按钮点击

JavaScript中的setInterval用法

使用 setInterval 调用的 Javascript 绑定

JavaScript中setInterval关闭问题

JavaScript中setInterval关闭问题

javascript 用 clearinterval 停止 setinterval