Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript closure : Memory Leak

I have a memory leak I don't understand. I programmed a mechanism to handle event with semi-automatically unbinding which should allow me to clean up memory easily. But in one case, the clean up doesn't happen (I use chrome's "profile (memory heap)" to check for instances of "EventHandler" left). I really don't get why it happen. There's something weird with the closure...

see it in action with chrome

function Bind(obj, f) {
    return function() {
        return f.apply(obj, arguments);
    }
}

function EventHandler() {
    this.listeners = new Object();

    var _listenerID = 0;
    this.addListener = function(e, obj, listener, specialDisplay) {
        if (typeof(listener) === "function") {
            var listenerID = ++_listenerID;
            console.log("Events (" + (++EventHandler.All) + ", " + listenerID + ") ++" + e);

            if (!this.listeners.hasOwnProperty(e)) {
                this.listeners[e] = new Object();
            }
            this.listeners[e][listenerID] = listener;

            if (obj != null && typeof(obj.removeListener) == "function") {
                var deleteListenerID = obj.addListener("Delete", null, Bind(this, function() {
                    this.removeListener(e, listenerID);
                    obj.removeListener("Delete", deleteListenerID);
                }));
            }

            return listenerID;
        }

        return null;
    }
    this.fire = function(e, obj) {
        if (this.listeners.hasOwnProperty(e)) {
            for(var i in this.listeners[e]) {
                this.listeners[e][i](obj);
            }
        }
    }
    this.removeListener = function(e, listenerID) {
        if (this.listeners.hasOwnProperty(e) && this.listeners[e].hasOwnProperty(listenerID)) {
            delete this.listeners[e][listenerID];

            console.log("Events (" + (--EventHandler.All) + ", " + listenerID + ") --" + e);
        }
    }
}

EventHandler.All = 0;

function Loader() {
}

Loader.files = new Object();

Loader.LoadImage = function(src, f) {
    if (!Loader.files.hasOwnProperty(src)) {
        var handler = new EventHandler();

        console.log("Loading.... (" + src + ")");

        Loader.files[src] = function(fnct) {
            handler.addListener("ImageLoaded", handler, function(img) {
                fnct(img);
            });
        }

        handler.addListener("ImageLoaded", handler, function() {
            Loader.files[src] = function(fnct) {
                fnct(img);
            }
        });     

        var img = new Image();
        $(img).load(function() {
            console.log("Loaded.... (" + src + ")");
            handler.fire("ImageLoaded", img);
            handler.fire("Delete");
            $(img).unbind('load');
        });
        img.src = src;
    }

    Loader.files[src](f);
}

Loader.LoadImage("http://serge.snakeman.be/Demo/house.jpg", function() { alert("ok"); });
like image 329
Serge Avatar asked Apr 07 '13 15:04

Serge


2 Answers

While you are adding 'listeners' make sure you are removing it, if you are using the query for long time.

this.listeners = new Object();

or

this.listeners[e] = new Object();

this will add object to listener as array, but not removing them at any point.

This may be the reason for Memory Consumption. It may not leak, its assignment of objects. that consumes your RAM using browser. :)

like image 146
MarmiK Avatar answered Oct 12 '22 01:10

MarmiK


You create closures that hold a reference to an EventHandler instance via the handler variable. One of the closures remains after the image has been loaded:

    handler.addListener("ImageLoaded", handler, function() {
        Loader.files[src] = function(fnct) {
            fnct(img);
        }
    });     

It's the inner function function(fnct) {.... The instance of EventHandler cannot be freed as long as the closure exists. Your only solution is to get rid of that closure. Or you free the instance manually, if possible. The following might work for you:

handler.fire("Delete");
handler = undefined;

Chrome's memory profiler shows you the object's retaining tree, which is just another way to say "Who is holding a reference that object". In your example it's EventHandler <- handler (the variable of the LoadImage method as incorporated by the closure) <- house.jpg, which actually is Loader.files[src] and has the value function(fnct) { fnct(img); }.

like image 37
a better oliver Avatar answered Oct 12 '22 01:10

a better oliver