69

現在、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() は 1 回しか呼び出されないため、割り当てられたメモリ サイズが増加する理由は何ですか?

(編集:アップロードされたクロムのスクリーンショット)

クロムのスクリーンショット

注 #1: はい、次の setInterval() の前に clearInterval() を呼び出してみました。問題は同じままです!

注 2: 問題を特定するために、上記のコードは単純で馬鹿げたものにしています。

4

8 に答える 8

54

編集: Yury の答えの方が優れています。


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 の一部ではありません)。実装は実装者に任されています。node.jsでsetIntervalなどの実装を確認することをお勧めします

于 2012-12-25T22:14:08.673 に答える
12

関数呼び出しを行うたびに、スタック フレームが作成されます。他の多くの言語とは異なり、Javascript は他のすべての言語と同様に、スタック フレームをヒープに格納します。これは、50 ミリ秒ごとに関数を呼び出すたびに、新しいスタック フレームがヒープに追加されることを意味します。これは合計され、最終的にガベージコレクションされます。

Javascript の仕組みを考えると、これは避けられないことです。それを軽減するために実際にできる唯一のことは、スタックフレームをできるだけ小さくすることです。これは、すべての実装で行われていると確信しています。

于 2013-02-12T18:11:19.137 に答える
8

setInterval とちらつきに関するコメントに返信したいと思います。

setInterval()、setTimeout()、さらには requestAnimationFrame を適切に使用すると、リクエストなしでメモリが割り当てられ、頻繁にガベージ コレクションが呼び出されることに気付きました。より多くの GC 呼び出し = ちらつき :-(

setInterval 呼び出しを、setTimeout に基づいたより害の少ない自己呼び出し関数に置き換えてみてください。ポール アイリッシュは、jQuery ソースから学んだ 10 の事柄と呼ばれる講演でこれについて言及しています (ビデオはこちら、ノート#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 の継続的な起動によって中断されないため、デバッグが少し簡単になります。

お役に立てれば!

于 2013-02-12T19:40:44.433 に答える
3

匿名関数なしでこれを試してください。例えば:

function draw()
{
    return true;
}

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

それはまだ同じように動作しますか?

于 2013-02-12T16:35:37.187 に答える
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
于 2012-12-25T22:44:46.563 に答える
2

メモリリークは発生していないようです。GC後にメモリ使用量が再び減少し、全体的なメモリ使用量が平均して増加傾向にない限り、リークは発生しません。

私がここで見ている「本当の」質問は、setInterval実際にメモリを使用して動作することであり、何かを割り当てる必要があるようには見えません。実際には、いくつかのものを割り当てる必要があります。

  1. 匿名関数とdraw()ルーチンの両方を実行するには、スタックスペースを割り当てる必要があります。
  2. 呼び出し自体を実行するために一時データを割り当てる必要があるかどうかはわかりません(おそらくそうではありません)
  3. trueからの戻り値を保持するために、少量のストレージを割り当てる必要がありますdraw()
  4. 内部的には、setIntervalは、繰り返し発生するイベントを再スケジュールするために追加のメモリを割り当てる場合があります(内部でどのように機能するかはわかりませんが、既存のレコードを再利用する場合があります)。
  5. JITはそのメソッドをトレースしようとする場合があります。これにより、トレースと一部のメトリックに追加のストレージが割り当てられます。VMは、このメソッドが小さすぎてトレースできないと判断する場合があります。トレースをオンまたはオフにするためのすべてのしきい値が正確にはわかりません。このコードをVMが「ホット」として識別するのに十分な時間実行すると、JITコンパイル済みマシンコードを保持するためにさらに多くのメモリが割り当てられる可能性があります(その後、生成されたマシンコードはほとんどの場合、より少ないメモリを割り当てます)

匿名関数が実行されるたびに、メモリが割り当てられます。これらの割り当てがしきい値に達すると、GCが起動してクリーンアップし、基本レベルに戻ります。サイクルは、シャットオフするまでこのように続きます。これは予想される動作です。

于 2013-02-12T16:46:24.390 に答える
1

私も同じ問題を抱えています。クライアントから、コンピューターのメモリがどんどん増えているとの報告がありました。最初は、単純なブラウザでアクセスできるのに、Web アプリでそれができるのはおかしいと思いました。これはChromeでのみ発生することに気付きました。

しかし、パートナーと一緒に調査を開始し、Chrome の開発者ツールとマネージャー タスクを通じて、クライアントから報告されたメモリの増加を確認できました。

次に、jquery 関数 (アニメーション フレームの要求) が何度も読み込まれ、システム メモリが増加していることがわかります。その後、この投稿のおかげで、jquery カウントダウンがそれを行っていることがわかりました。これは、アプリのレイアウトの日付を毎回更新する「SETINTERVAL」内にあるためです。

ASP.NET MVC を使用しているので、BundleConfig とレイアウトからこの jquery スクリプトのカウントダウンを終了し、時間のカウントダウンを次のコードに置き換えます。

@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))
于 2014-11-17T16:45:22.223 に答える