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

mat*_*ari 62 javascript memory-leaks setinterval

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

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

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

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

function draw()
{
    return true
}
Run Code Online (Sandbox Code Playgroud)

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

(编辑:chrome截图上传)

chrome截图

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

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

Luq*_*aan 52

编辑: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();
Run Code Online (Sandbox Code Playgroud)

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

function b() {
    var a = setInterval(function() {
        console.log("Hello");
        clearInterval(a);
        b();                
    }, 50);
}
b();
Run Code Online (Sandbox Code Playgroud)

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

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

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();?
Run Code Online (Sandbox Code Playgroud)

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

显然setTimeout,setInterval并不是Javascript的正式部分(因此它们不是v8的一部分).实施由实施者决定.我建议你看看node.js中setInterval的实现

  • 是的,正如我在原始邮件中发布的那样,我已经知道垃圾收集了.有用.我无法理解的是,为什么单个计时器方法(如setInterval())会继续占用内存?从你的JSFiddle,我可以看到,从1.1分钟到4.8分钟,内存分配上升(不断增加)!它要求更多的内存=更多的GC调用被激活!为了阻止不必要的GC调用,需要"驯服"setInterval()以便它停止分配内存...... (4认同)
  • 我怀疑这是堆栈,因为您在这里处理的不是递归,而是异步操作。 (2认同)

Yur*_*sky 29

这里的问题不在代码本身,它不泄漏.这是因为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


ICR*_*ICR 12

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

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


mrd*_*rdc 6

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

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

您可能想尝试使用基于setTimeout 的较不邪恶的自调用函数替换setInterval调用.保罗爱尔兰提到这在谈话称为10件事情我从jQuery源教训(视频这里,注意到这里看到#2).你所做的是将你对setInterval的调用替换为一个函数,该函数在完成它应该做的工作之后通过setTimeout间接调用它自己.引用话题:

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

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

function init() 
{
    var ref = window.setInterval(function() { draw(); }, 50);
}
Run Code Online (Sandbox Code Playgroud)

至:

function init()
{
     //init stuff

     //awesome code

     //start rendering
     drawLoop();
}

function drawLoop()
{
   //do work
   draw();

   //queue more work
   setTimeout(drawLoop, 50);
}
Run Code Online (Sandbox Code Playgroud)

这应该有点帮助,因为:

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

希望这可以帮助!