繁体   English   中英

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

[英]Does JavaScript setInterval() method cause memory leak?

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

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

例如; 当我通过在谷歌浏览器中调用init()执行以下简单代码时 ,内存分配+垃圾收集在前20-30秒内没问题......

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

function draw()
{
    return true
}

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

(编辑:chrome截图上传)

chrome截图

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

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

编辑: Yury的答案更好。


tl;博士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的一部分)。 实施由实施者决定。 我建议你看看node.js中setInterval的实现

这里的问题不在代码本身,它不泄漏。 这是因为Timeline面板的实现方式。 当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

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

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

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

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

您可能想尝试使用基于setTimeout的较不邪恶的自调用函数替换setInterval调用。 保罗爱尔兰提到这在谈话称为10件事情我从jQuery源教训(视频这里 ,注意到这里看到#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);
}

这应该有点帮助,因为:

  1. draw()将不会被渲染循环再次调用,直到它完成
  2. 正如上面的许多答案所指出的那样,来自setInterval的所有不间断函数调用确实会在浏览器上产生开销。
  3. 调试更容易,因为你没有被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);
}

它的行为方式是否仍然相同?

似乎没有内存泄漏。 只要在GC之后内存使用量再次减少,并且总体内存使用率平均不会向上趋势,就没有泄漏。

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

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

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

我也有同样的问题。 客户报告我,计算机的内存每次都在增加。 起初我认为,即使它是由一个简单的浏览器访问,一个Web应用程序可以做到这一点真的很奇怪。 我注意到这只发生在Chrome中。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM