現在、JavaScriptベースのアニメーションプロジェクトを開発しています。
setInterval()
、setTimeout()
、さらにはrequestAnimationFrame
を適切に使用すると、リクエストなしでメモリが割り当てられ、ガベージコレクションの呼び出しが頻繁に発生することに気付きました。より多くのGC呼び出し=フリッカー:
例えば; Google Chromeでinit()を呼び出して次の---(シンプルコードを実行すると、メモリ割り当て+ガベージコレクションは最初の20〜30秒間は問題ありません...
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
return true
}
どういうわけか、1分程度で、割り当てられたメモリの奇妙な増加が始まります! init()は一度だけ呼び出されるため、割り当てられたメモリサイズが増加する理由は何ですか?
(編集:chromeスクリーンショットがアップロードされました)
注#1:はい、次のsetInterval()の前にclearInterval()を呼び出してみました。問題は同じままです!
注#2:問題を切り分けるために、上記のコードはシンプルで愚かです。
編集: ユーリーの答え の方が良い。
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/
どうやらsetTimeout
とsetInterval
は公式にはJavascriptの一部ではないようです(したがって、v8の一部ではありません)。実装は実装者に任されています。 node.jsのsetIntervalなどの実装 をご覧になることをお勧めします
ここでの問題はコード自体にあるのではなく、漏れはありません。これは、タイムラインパネルの実装方法が原因です。タイムラインがイベントを記録するとき、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は他のすべてと同様に、スタックフレームをヒープに格納します。つまり、50ミリ秒ごとに実行している関数を呼び出すたびに、新しいスタックフレームがヒープに追加されます。これは合計され、最終的にガベージコレクションされます。
Javascriptの仕組みを考えると、それはやむを得ないことです。それを軽減するために本当にできる唯一のことは、スタックフレームを可能な限り小さくすることです。これは、すべての実装が行うと確信しています。
SetIntervalとちらつきについてのコメントに返信したいです。
SetInterval()、setTimeout()、requestAnimationFrameを適切に使用すると、リクエストなしでメモリが割り当てられ、ガベージコレクションの呼び出しが頻繁に発生することに気付きました。より多くのGC呼び出し=フリッカー:
SetInterval呼び出しを、less evil setTimeoutに基づく自己呼び出し関数に置き換えてみてください。 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);
}
これは少し役立つはずです:
お役に立てれば!
無名関数なしでこれを試してください。例えば:
function draw()
{
return true;
}
function init()
{
var ref = window.setInterval(draw, 50);
}
それでも同じように動作しますか?
Chromeは、プログラムからのメモリプレッシャーをほとんど認識していません(1.23 MBは、今日の標準ではメモリ使用量が非常に少ないため)。より多くのメモリを使用するようにプログラムを変更すると、ガベージコレクターが起動します。これを試して:
<!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後に再び減少し、全体的なメモリ使用量が平均して増加傾向にない限り、リークはありません。
ここで私が見ている「本当の」質問は、setInterval
が実際に動作するためにメモリを使用していることであり、何かを割り当てる必要があるようには見えません。実際には、いくつかのことを割り当てる必要があります。
draw()
からのtrue
戻り値を保持するために、少量のストレージを割り当てる必要があります。匿名関数を実行するたびに、メモリが割り当てられます。これらの割り当てがしきい値に達すると、GCが起動してクリーンアップし、ベースレベルに戻ります。サイクルは、停止するまでこのように続きます。これは予想される動作です。
私も同じ問題を抱えています。クライアントは、コンピューターのメモリがどんどん増えていると報告しました。最初は、単純なブラウザでアクセスされていても、Webアプリでそれができるのは本当に奇妙だと思いました。これはChromeでのみ発生していることに気付きました。
しかし、私はパートナーと一緒に調査を開始し、Chromeとマネージャータスクを使用して、クライアントから報告されたメモリの増加を確認できました。
次に、jquery関数(アニメーションフレームの要求)がロードされ、システムメモリが何度も増加していることがわかります。その後、この投稿のおかげで、jqueryカウントダウンがそれを行っていました。これは、毎回アプリのレイアウトの日付を更新する「SETINTERVAL」内にあるためです。
ASP.NET MVCで作業しているときに、BundleConfigおよびレイアウトからこのjqueryスクリプトカウントダウンを終了し、時間のカウントダウンを次のコードに置き換えました。
@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))