48

We know node.js provides us with great power but with great power comes great responsibility.

As far as I know the V8 engine doesn't do any garbage collection. So what are the most common mistakes we should avoid to ensure that no memory is leaking from my node server.

EDIT: Sorry for my ignorance, V8 does have a powerful garbage collector.

mikl
  • 23,749
  • 20
  • 68
  • 89
neebz
  • 11,465
  • 7
  • 47
  • 64
  • 8
    Wait, wat? A JS implementation (or more generally, *any* implementation of a language where manual memory management is taken out of the programmers' hands) without GC seems pretty worthless to me. And in fact, Google showed me http://code.google.com/apis/v8/design.html#garb_coll as the very first result. Where did you get the "V8 doesn't do GC" idea? –  Apr 20 '11 at 16:40
  • 6
    V8 has an ephemeral and linear garbage collector that stops the world when it sweeps. Implying it has no GC is nonsense. In fact, it's one of the best JS GCs we have. Another great one is in IE9+. Mozilla is going to improve their GC design in the future, I heard, towards V8. – Tower Jun 30 '11 at 13:12

3 Answers3

67

As far as I know the V8 engine doesn't do any garbage collection.

V8 has a powerful and intelligent garbage collector in build.

Your main problem is not understanding how closures maintain a reference to scope and context of outer functions. This means there are various ways you can create circular references or otherwise create variables that just do not get cleaned up.

This is because your code is ambigious and the compiler can not tell if it is safe to garbage collect it.

A way to force the GC to pick up data is to null your variables.

function(foo, cb) {
    var bigObject = new BigObject();
    doFoo(foo).on("change", function(e) {
         if (e.type === bigObject.type) {
              cb();
              // bigObject = null;
         }
    });
}

How does v8 know whether it is safe to garbage collect big object when it's in an event handler? It doesn't so you need to tell it it's no longer used by setting the variable to null.

Various articles to read:

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 24
    Circular references shouldn't pose the slightest problem to an "intelligent" GC. Even the simplest GCs can handle them (refcounting is not a real GC). The hint about event handlers seems correct though. –  Apr 20 '11 at 16:45
  • @delnan Ah yes the circular reference leaks were for IE6/7. I also meant circular reference in callbacks, closures and event handlers not just `a = b; b = a;` Could you recommend good resources on identifying memory leaks for closures? – Raynos Apr 20 '11 at 16:49
  • +1 @delnan and as far as closures go, it's not a memory leak, rather a language feature that people often do not get and Raynos answer is right in that sense. – Tower Jun 30 '11 at 13:17
  • 1
    Is it necessary to null "bigObject" inside of a function scope. Shouldn't GC take care of it once the function is complete? – jwerre Oct 15 '12 at 03:31
  • 3
    @jwerre that function is never complete. It's a change listener. That function will only be complete if the event emitter returned by `doFoo(foo)` get's garbage collected. – Raynos Oct 15 '12 at 07:40
  • 4
    This is really good. Raynos said there were various other ways to create problems like this; does anybody have a good guide on other things to look out for? – Zane Claes Oct 16 '12 at 00:09
  • 5
    @Raynos Setting `bigObject` to `null` doesn't make any sense in this situation in my opinion. Removing the event handler itself will allow `bigObject` to be collected and that's what we want. However to only *null* the `bigObject` reference will just cause a runtime error on the next handler invocation. – plalx Oct 30 '13 at 18:57
  • 1
    @Raynos Not only this but you will still be leaking memory if the handler is not removed since the handler itself would never get garbage collected even when you no longer need it. – plalx Oct 30 '13 at 19:09
  • Great answer, but how does the GC run exactly? Does it run every xx seconds, or is just automatic after a code block is run? – NiCk Newman Jun 02 '15 at 19:19
  • If it's necessary to null bigObject is it necessary to null cb too? – kequc Feb 16 '16 at 23:34
24

I wanted to convince myself of the accepted answer, specifically:

not understanding how closures maintain a reference to scope and context of outer functions.

So I wrote the following code to demonstrate how variables can fail to be cleaned up, which people may find of interest.

If you have watch -n 0.2 'ps -o rss $(pgrep node)' running in another terminal you can watch the leak occurring. Note how commenting in either the buffer = null or using nextTick will allow the process to complete:

(function () {
    "use strict";

    var fs = require('fs'),
        iterations = 0,

        work = function (callback) {
            var buffer = '',
                i;

            console.log('Work ' + iterations);

            for (i = 0; i < 50; i += 1) {
                buffer += fs.readFileSync('/usr/share/dict/words');
            }

            iterations += 1;
            if (iterations < 100) {
                // buffer = null;

                // process.nextTick(function () {
                    work(callback);
                // });
            } else {
                callback();
            }
        };

    work(function () {
        console.log('Done');
    });

}());
davetapley
  • 17,000
  • 12
  • 60
  • 86
  • 1
    I, too, tend to learn, understand, and accept things best when I can visualize them somehow; this was a good example that allowed me to do just that - to see the variation in memory as GC occurs. Thanks for sharing. – Brandon K Aug 15 '13 at 05:20
  • 4
    @dukedave I am not sure if that's a good example. Obvioulsy the local `buffer` variable cannot get garbage collected before the recursive function call ends, unless you explicitly *null* it but can this behaviour really considered a leak? When the recursive call will end all the local variables will naturally be eligible for GC wheter you *null* the `buffer` or not. No? – plalx Oct 30 '13 at 19:22
  • @plalx I'd consider it a leak insomuch as the application programmer is concerned (i.e. they didn't realize that `buffer` would grow). Perhaps I should have been more clear that this was addressing the accepted answer's statement that, "Your main problem is not understanding how closures maintain a reference to scope and context of outer functions"; I'll update my answer to reflect that now. – davetapley Oct 31 '13 at 00:11
  • great answer with simple example! – karthikeayan Oct 30 '14 at 13:57
  • there's no leak, it's a recursive call. a very inteligent compiler could be smarter than you and just remove buffer usage since you're doing nothing with it :) – dresende May 03 '15 at 18:35
9

active garbage collection with:

node --expose-gc test.js

and use with:

global.gc();

Happy Coding :)

kazelsama
  • 136
  • 1
  • 4
  • 7
    Manually calling the garbage collector will not help with a real memory leak. The garbage collector is called periodically by the runtime anyway and a memory leak in a GCed language is caused by creating references that the garbage collector cannot safely collect. *However* when debugging, calling the gc frequently can greatly increase the signal to noise ratio and make it much easier to tell if you have a real memory leak. – Gareth Charnock Jun 26 '15 at 08:43