Romain Beaudon

Backbone Memory leaks analysis with Chrome Heap Profiler

Intro

The Heap Profiler in Chrome is a great tool to analyze memory usage in web applications. Here are some useful links :

Why do we have to worry about memory management in JS when there is a Garbage Collector ? Because memory leaks can still happen : as long as an object is referenced by another it cannot be disposed by the GC. These references are “Retaining Paths” in the Heap Profiler.

A common source of these problems are event binding : events are references, which prevent object to be destroyed after they are removed from the DOM. This can happen with Backbone Views.

Example

Here is a simple example (using the Backbone global object as an event mediator) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>
<body>
    <script src="jquery.js"></script>
    <script src="underscore.js"></script>
    <script src="backbone.js"></script>
    <script src="zombieViews.js"></script>

    <script>

        var ZombieView = Backbone.View.extend({
            initialize: function() {
                Backbone.on('greetingEvent', this.sayFoo, this);
            },

            sayFoo: function() {
                console.log('foo');
            }
        });

        for(var i = 0; i<1000; ++i) {
            var view = new ZombieView();
            view.remove();
        }

        Backbone.trigger('greetingEvent');

    </script>
</body>
</html>

Console Ouptput :

Nothing should appear in the console because every view is removed after its creation. As we can see this is not the case : the views are still catching greetingEvent and displaying a message.

A heap snapshot confirm that all the ZombieViews are still here (extended Backbone models have their constructor named child, see this post for a solution).

Why the views aren’t removed ?

Troobleshoot

If we take a heap snapshot we can see that the greetingEvent is in the retaining path of the views. This path prevent the GC to dispose the object.

Let’s modify the code with a call to listenTo instead of Backbone.on. With listenTo models can keep track of events binded so they can be unbinded by the remove method and thus allow the GC to dispose the attached views.

1
2
//Backbone.on('greetingEvent', this.sayFoo, this);
this.listenTo(Backbone, 'greetingEvent', this.sayFoo);

This time nothing shows up in the console, which is the expected output : all the views where removed, none catched the event.

Note: You may have noted that the views have their constructor named ‘child’ which complicate identification, a solution is provided in this post.