ChromeでJavaScriptのメモリリークを見つける 質問する

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 のプロファイラーを使用して、これが実際に当てはまるかどうかを確認する方法がわかりません。ヒープ プロファイラーのスナップショットには膨大な数が表示され、何が良いか悪いかを解読する方法がわかりません。これまでに見たチュートリアルでは、「スナップショット プロファイラーを使用する」ように指示されているか、プロファイラー全体の動作に関する非常に詳細なマニフェストが提供されています。プロファイラーをツールとして使用するだけでよいのでしょうか、それとも、全体がどのように設計されているかを理解する必要があるのでしょうか。

編集:次のようなチュートリアル:

Gmail のメモリリークの修正

DevToolsの使用

私が見た限りでは、これらは最も強力な素材の代表例です。しかし、3 スナップショットテクニック、実用的な知識という点では、(私のような初心者にとっては)ほとんど提供されていないと思います。「DevTools の使用」チュートリアルは実際の例を通して機能しないため、漠然とした一般的な概念の説明はあまり役に立ちません。「Gmail」の例については、次のようになります。

漏れを発見しました。次は何をすればいいですか?

  • Examine the retaining path of leaked objects in the lower half of the Profiles panel

  • If the allocation site cannot be easily inferred (i.e. event listeners):

  • Instrument the constructor of the retaining object via the JS console to save the stack trace for allocations

  • Using Closure? Enable the appropriate existing flag (i.e. goog.events.Listener.ENABLE_MONITORING) to set the creationStack property during construction

I find myself more confused after reading that, not less. And, again, it's just telling me to do things, not how to do them. From my perspective, all of the information out there is either too vague or would only make sense to someone who already understood the process.

Some of these more specific issues have been raised in @Jonathan Naguin's answer below.

ベストアンサー1

A good workflow to find memory leaks is the three snapshot technique, first used by Loreena Lee and the Gmail team to solve some of their memory problems. The steps are, in general:

  • Take a heap snapshot.
  • Do stuff.
  • Take another heap snapshot.
  • Repeat the same stuff.
  • Take another heap snapshot.
  • Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.

For your example, I have adapted the code to show this process (you can find it here) delaying the creation of the Backbone View until the click event of the Start button. Now:

  • Run the HTML (saved locally of using this address) and take a snapshot.
  • Click Start to create the view.
  • Take another snapshot.
  • Click remove.
  • Take another snapshot.
  • Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.

Now you are ready to find memory leaks!

You will notice nodes of a few different colors. Red nodes do not have direct references from Javascript to them, but are alive because they are part of a detached DOM tree. There may be a node in the tree referenced from Javascript (maybe as a closure or variable) but is coincidentally preventing the entire DOM tree from being garbage collected.

ここに画像の説明を入力してください

Yellow nodes however do have direct references from Javascript. Look for yellow nodes in the same detached DOM tree to locate references from your Javascript. There should be a chain of properties leading from the DOM window to the element.

In your particular you can see a HTML Div element marked as red. If you expand the element you will see that is referenced by a "cache" function.

ここに画像の説明を入力してください

Select the row and in your console type $0, you will see the actual function and location:

>$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

This is where your element is being referenced. Unfortunally there is not much you can do, it is a internal mechanism from jQuery. But, just for testing purpose, go the function and change the method to:

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

Now if you repeat the process you will not see any red node :)

Documentation:

おすすめ記事