web-dev-qa-db-ja.com

Chromeを使用してJavaScriptのメモリリークを見つける

Backboneビューを作成し、ハンドラーをイベントにアタッチし、ユーザー定義のクラスをインスタンス化する非常に簡単なテストケースを作成しました。このサンプルの[削除]ボタンをクリックすると、すべてがクリーンアップされ、メモリリークが発生しないはずです。

コードのjsfiddleは次のとおりです。 http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

ただし、Google Chromeのプロファイラーを使用して、これが事実であることを確認する方法は不明です。ヒーププロファイラのスナップショットには膨大な数の事柄が表示されますが、良い点と悪い点をデコードする方法はわかりません。これまでに見てきたチュートリアルでは、「スナップショットプロファイラーを使用する」ように指示するか、プロファイラー全体がどのように機能するかについて非常に詳細なマニフェストを提供します。プロファイラーをツールとして使用することは可能ですか、それとも全体がどのように設計されたかを本当に理解する必要がありますか?

EDIT:これらのようなチュートリアル:

Gmailメモリリークの修正

DevToolsを使用

私が見たものから、そこにあるより強い材料のいくつかの代表です。ただし、3スナップショットテクニックの概念を紹介する以外に、実用的な知識に関してはほとんど提供していません(私のような初心者向け)。 「DevToolsの使用」チュートリアルは実際の例では機能しないため、曖昧で一般的な概念の説明はあまり役に立ちません。 「Gmail」の例に関して:

だから、リークを見つけました。今何?

  • [プロファイル]パネルの下半分で、漏れたオブジェクトの保持パスを調べます

  • 割り当てサイトを簡単に推測できない場合(つまり、イベントリスナー):

  • JSコンソールを介して保持オブジェクトのコンストラクターをインスツルメントして、割り当てのスタックトレースを保存します。

  • クロージャーを使用していますか?適切な既存のフラグ(つまりgoog.events.Listener.ENABLE_MONITORING)を有効にして、作成中にcreationStackプロパティを設定します

それを読んだ後、私はもっと混乱していると感じています。そして、繰り返しますが、それはdo事ではなく、howするように私に言っているだけです。私の観点からは、そこにあるすべての情報は曖昧すぎるか、すでにプロセスを理解している人にしか意味がありません。

これらのより具体的な問題のいくつかは、以下の @ Jonathan Naguin's answer で提起されています。

152
EleventyOne

メモリリークを見つけるための適切なワークフローは、3スナップショットテクニックです。最初にLoreena LeeとGmailチームがメモリの問題を解決するために使用しました。一般的な手順は次のとおりです。

  • ヒープスナップショットを作成します。
  • 物事を行います。
  • 別のヒープスナップショットを作成します。
  • 同じことを繰り返します。
  • 別のヒープスナップショットを作成します。
  • スナップショット3の「概要」ビューで、スナップショット1と2の間に割り当てられたオブジェクトをフィルターします。

あなたの例では、このプロセスを表示するようにコードを調整しました(あなたはそれを見つけることができます here )開始ボタンのクリックイベントまでバックボーンビューの作成を遅らせます。今:

  • HTML(この address を使用してローカルに保存)を実行し、スナップショットを作成します。
  • [開始]をクリックして、ビューを作成します。
  • 別のスナップショットを撮ります。
  • 削除をクリックします。
  • 別のスナップショットを撮ります。
  • スナップショット3の「概要」ビューで、スナップショット1と2の間に割り当てられたオブジェクトをフィルターします。

これで、メモリリークを見つける準備ができました!

いくつかの異なる色のノードがあります。赤いノードにはJavascriptから直接の参照はありませんが、切り離されたDOMツリーの一部であるため、生きています。 Javascriptから参照されたノード(クロージャーまたは変数として)が存在する場合がありますが、偶然DOMツリー全体がガベージコレクションされるのを防ぎます。

enter image description here

ただし、黄色のノードにはJavascriptからの直接参照があります。同じデタッチされたDOMツリーで黄色のノードを探し、Javascriptから参照を見つけます。 DOMウィンドウから要素につながる一連のプロパティが必要です。

あなたの特定では、赤としてマークされたHTML Div要素を見ることができます。要素を展開すると、「キャッシュ」関数によって参照されていることがわかります。

enter image description here

行を選択し、コンソールに「$ 0」と入力すると、実際の機能と場所が表示されます。

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.Push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

これは、要素が参照される場所です。残念ながら、できることはあまりありません。これはjQueryの内部メカニズムです。ただし、テスト目的のためだけに、関数に移動してメソッドを次のように変更します。

function cache( key, value ) {
    return value;
}

プロセスを繰り返しても、赤いノードは表示されません:)

ドキュメンテーション:

193
Jonathan Naguin

Jsfiddleのメモリプロファイリングに関するヒントを次に示します。次のURLを使用してjsfiddleの結果を分離し、すべてのjsfiddleフレームワークを削除して、結果のみを読み込みます。

http://jsfiddle.net/4QhR2/show/

次のドキュメントを読むまで、タイムラインとプロファイラーを使用してメモリリークを追跡する方法を理解することはできませんでした。 「オブジェクト割り当てトラッカー」というタイトルのセクションを読んだ後、「ヒープ割り当ての記録」ツールを使用して、いくつかの切り離されたDOMノードを追跡できました。

JQueryイベントバインディングからBackboneイベントデリゲーションの使用に切り替えることで問題を修正しました。 View.remove()を呼び出すと、Backboneの新しいバージョンが自動的にイベントのバインドを解除することを理解しています。いくつかのデモを自分で実行すると、特定できるようにメモリリークが設定されます。このドキュメントを調べてもまだわからない場合は、ここで質問してください。

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling

7
ricksuggs

基本的に、ヒープスナップショット内のオブジェクトの数を調べる必要があります。 2つのスナップショット間でオブジェクトの数が増え、オブジェクトを破棄した場合、メモリリークが発生します。私のアドバイスは、切り離されないコード内のイベントハンドラーを探すことです。

6

Googleの紹介ビデオがあり、JavaScriptのメモリリークを見つけるのに非常に役立ちます。

https://www.youtube.com/watch?v=L3ugr9BJqIs

4
令狐葱

開発者ツールの[タイムライン]タブもご覧ください。アプリの使用状況を記録し、DOM Nodeおよびイベントリスナーカウントに注目してください。

メモリグラフが実際にメモリリークを示している場合は、プロファイラを使用して何がリークしているかを把握できます。

3
Robert Falkén

また読むことをお勧めします:

http://addyosmani.com/blog/taming-the-Unicorn-easing-javascript-memory-profiling-in-devtools/

chrome開発者ツールの使用について説明し、ヒープスナップショット比較と利用可能なさまざまなヒープスナップショットビューを使用してメモリリークを確認および特定する方法について、段階的なアドバイスを提供します。

3
bennidi

ヒープスナップショットを取得するための2番目のアドバイスは、メモリリークの検出に優れています。chromeはスナップショットの優れた仕事をします。

私の学位の研究プロジェクトでは、「レイヤー」に蓄積された多くのデータを生成する必要があるインタラクティブなWebアプリケーションを構築していました。これらのレイヤーの多くはUIで「削除」されますが、何らかの理由でメモリがスナップショットツールを使用して割り当てを解除すると、JQueryがオブジェクトの参照を保持していたことを確認できました(ソースは、スコープ外になっても参照を保持する.load()イベントをトリガーしようとしたときでした)。この情報を手に持って私のプロジェクトを片手で保存できるので、他の人のライブラリを使用していて、GCが仕事をするのを妨げる参照が残るという問題がある場合に非常に便利なツールです。

編集:実行するアクションを事前に計画して、スナップショットに費やす時間を最小限に抑え、問題の原因を推測し、各シナリオをテストして、前後にスナップショットを作成することも役立ちます。