目前正在开发基于JavaScript的动画项目。
我注意到,正确使用setInterval()
,setTimeout()
甚至requestAnimationFrame
会在没有请求的情况下分配内存,并导致频繁的垃圾回收调用。更多GC调用=闪烁: - (
例如;当我在谷歌浏览器中调用init()执行以下简单代码时,内存分配+垃圾收集在前20-30秒内没问题......
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
return true
}
不知何故,在一分钟左右的时间内,分配的内存开始出现奇怪的增加!由于init()仅被调用一次,分配内存大小增加的原因是什么?
(编辑:Chrome截图已上传)
注意#1:是的,我尝试在下一个setInterval()之前调用clearInterval()。问题依然存在!
注意#2:为了解决问题,我保持上面的代码简单而愚蠢。
答案 0 :(得分:51)
编辑:Yury's answer更好。
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/
显然setTimeout
和setInterval
不是Javascript的正式部分(因此它们不是v8的一部分)。实施由实施者决定。我建议你看看the implementation of setInterval and such in node.js
答案 1 :(得分:27)
这里的问题不在于代码本身,它不会泄漏。这是因为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
答案 2 :(得分:12)
每次进行函数调用时,都会创建stack frame。与许多其他语言不同,Javascript将堆栈帧存储在堆上,就像其他所有语言一样。这意味着每次调用一个每隔50ms执行一次的函数时,就会在堆中添加一个新的堆栈帧。这加起来并最终被垃圾收集。
考虑到Javascript的工作方式,这是不可避免的。唯一可以真正做到的就是减轻它的作用是使堆栈帧尽可能小,我相信所有的实现都会这样做。
答案 3 :(得分:6)
我想回复你关于setInterval和闪烁的评论:
我注意到,正确使用setInterval(),setTimeout()甚至requestAnimationFrame都会在没有请求的情况下分配内存,并导致频繁的垃圾回收调用。更多GC调用=闪烁: - (
您可能希望尝试使用基于setTimeout的 less evil 自调用函数替换setInterval调用。保罗·爱尔兰在谈话中提到了这一点,我从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);
}
这应该有所帮助,因为:
希望这有帮助!
答案 4 :(得分:3)
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
答案 5 :(得分:3)
尝试在没有匿名功能的情况下执行此操作。例如:
function draw()
{
return true;
}
function init()
{
var ref = window.setInterval(draw, 50);
}
它的行为方式是否仍然相同?
答案 6 :(得分:2)
似乎没有内存泄漏。只要在GC之后内存使用量再次减少,并且整体内存使用率平均不会上升,就没有泄漏。
我在这里看到的“真实”问题是setInterval
确实使用内存来操作,并且看起来它不应该分配任何内容。实际上,它需要分配一些东西:
true
的{{1}}返回值。每次执行匿名函数时,都会分配一些内存。当这些分配加起来达到某个阈值时,GC将启动并清理以使您回到基准级别。循环将继续这样,直到你关闭它。这是预期的行为。
答案 7 :(得分:1)
我也有同样的问题。客户报告我,计算机的内存每次都在增加。起初我认为,即使它是由一个简单的浏览器访问,一个Web应用程序可以做到这一点真的很奇怪。我注意到这只发生在Chrome中。
然而,我开始与合作伙伴一起调查并通过Chrome的开发者工具和经理任务,我们可以看到客户报告我的内存增加。
然后我们看到一个jquery函数(请求动画帧)一遍又一遍地加载系统内存。在那之后,我们看到感谢这篇文章,jquery倒计时正在这样做,因为它内部有一个“SETINTERVAL”,每次都在我的应用程序布局中更新日期。
当我使用ASP.NET MVC时,我刚从BundleConfig退出此jquery脚本倒计时,并从我的布局中用以下代码替换我的时间倒计时:
@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))