1. ホーム
  2. javascript

[解決済み] ChromeでJavaScriptのメモリリークを発見する

2022-04-23 01:30:26

質問

私は、Backboneのビューを作成し、イベントにハンドラをアタッチし、ユーザー定義クラスをインスタンス化する非常に単純なテストケースを作成しました。私は、このサンプルの"Remove"ボタンをクリックすることによって、すべてがクリーンアップされ、メモリリークがないはずだと信じています。

このコードの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コンソールを介してretainオブジェクトのコンストラクタをインストルメントし、アロケーションのスタックトレースを保存します。

  • クロージャを使用する場合 適切な既存のフラグ (たとえば goog.events.Listener.ENABLE_MONITORING) を有効にして、構築中に creationStack プロパティを設定します。

それを読んだ後では、混乱はなくなるどころか、もっと混乱していることに気づきました。そしてまた、それは私にこう言っているのです。 する のことであって いかに を行うことです。私の目から見ると、世の中の情報はどれも漠然としているか、すでにそのプロセスを理解している人にしか理解できないものばかりです。

このような、より具体的な問題点については、いくつか ジョナサン・ナギンの回答 以下のとおりです。

どのように解決するのですか?

メモリリークを発見するための良いワークフローは スリースナップショット このテクニックは、Loreena LeeとGmailチームがメモリ問題を解決するために最初に使用したものです。その手順は、一般的に

  • ヒープスナップショットを取得します。
  • 何かをする。
  • 別のヒープスナップショットを取得します。
  • 同じことを繰り返す。
  • 別のヒープスナップショットを取る。
  • スナップショット3のquot;Summary"ビューで、スナップショット1と2の間に割り当てられたオブジェクトをフィルタリングします。

あなたの例のために、このプロセスを示すコードを適応しました(あなたはそれを見つけることができます ここで ) Backbone Viewの作成をStartボタンのクリックイベントまで遅らせています。さて

  • HTMLを実行します。 アドレス )し、スナップショットを取る。
  • Startをクリックして、ビューを作成します。
  • 別のスナップショットを撮る。
  • 削除をクリックします。
  • 別のスナップショットを撮る。
  • スナップショット3のquot;Summary"ビューで、スナップショット1と2の間に割り当てられたオブジェクトをフィルタリングします。

これでメモリリークを発見する準備が整いました!

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

ただし、黄色いノードはJavascriptから直接参照されています。同じように切り離されたDOMツリーで黄色のノードを探し、Javascriptからの参照を見つけます。DOM ウィンドウから要素につながるプロパティの連鎖があるはずです。

あなたのこだわりでは、赤でマークされたHTMLのDiv要素が見えると思います。その要素を展開すると、それが "cache" 関数によって参照されていることがわかります。

行を選択し、コンソールに「$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;
}

これで、このプロセスを繰り返すと、赤いノードが表示されなくなります :)

ドキュメンテーションです。