9

I have a gameserver.js file that is well over 100 KB in size. And I kept checking my task manager after each refresh on my browser and kept seeing my node.exe memory usage keep rising for every refresh. I'm using the ws module here: https://github.com/websockets/ws and figured, you know what, there is most likely some memory leak in my code somewhere...

So to double check and isolate the issue I created a test.js file and put in the default ws code block:

var WebSocketServer = require('ws').Server
  , wss = new WebSocketServer({ port: 9300 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });
});

And started it up:

Enter image description here

Now, I check node.exe's memory usage:

Enter image description here

The incremental part that makes me confused is:

If I refresh my browser that makes the connection to this port 9300 websocket server and then look back at my task manager.. it shows:

Enter image description here

Which is now at: 14,500 K.

And it keeps on rising upon each refresh, so theoretically if I keep just refreshing it will go through the roof. Is this intended? Is there a memory leak in the ws module somewhere maybe? The whole reason I ask is because I thought maybe in a few minutes or when the user closes the browser it will go back down, but it doesn't.

And the core reason why I wanted to do this test because I figured I had a memory leak issue in my personal code somewhere and just wanted to check if it wasn't me, or vice versa. Now I'm stumped.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
NiCk Newman
  • 1,716
  • 7
  • 23
  • 48
  • 1
    Have you tried actually closing those connections on the server side instead of just letting them time out idling? While they are open, they require memory to track. If neither your browser code nor your server code explicitly closes them, they have to timeout which can take a while. Even then, GC is passive not aggressive, it's not going to run and free up memory in an intuitive "using less memory right away is better" way. It doesn't work like that. – Peter Lyons Jun 02 '15 at 23:35
  • Aww ic. [Check here](https://github.com/websockets/ws/blob/master/doc/ws.md#event-close) the arguments they pass are only the message and the code so I don't think I can specifically close a connection and just have to wait for the timeout like you said. I get what you're saying though, appreciate it. I'll try to find a way to close them instantly and not wait for a timeout. That makes sense as why the memory usage is increasing. – NiCk Newman Jun 02 '15 at 23:45
  • @Peter Lyons I take that back, there is a way. It's: `socket.close();` [full code](https://jsfiddle.net/7y5fsmey/1/) and I use it on the 'onclose' handler. Problem is, even after doing this the memory usage doesn't go down either.. Hmmm – NiCk Newman Jun 03 '15 at 00:06
  • It should be noted that the GC in v8 is somewhat lazy and typically will not garbage collect immediately if it doesn't need to. A better method is to use automation to test opening and closing of the sockets over a long period of time. – mscdex Jun 03 '15 at 03:13
  • I have found the issue in my code but not going to post a answer because [Robert](http://stackoverflow.com/users/2019265/robert-rossmann) already has the best one theoretically. But in my case, the socket object is actually NOT being DELETED but being left-over to be garbaged collected at a later date. [See Here](https://github.com/websockets/ws/issues/450#issuecomment-108224443) for the official post on this. So, what can we take away from this? The socket object DATA is not deleted after `socket.close()`, and that object data is still stored in memory,that's why we see the increasing.. – NiCk Newman Jun 03 '15 at 17:18
  • ..continued: memory usage consumption per refresh. Because for this ws module specifically, each new refresh or connection adds a new socket object and that data DOES NOT get deleted until the V8 Garbage Collector deletes it. So every new connection, there is another floating socket object with all that data in memory. Which is technically a memory leak for the time it exists, but the V8 JS takes care of it sooner or later. My 2 cents anyways. I would rather prefer the WS module to just delete the socket object properties upon closing though.... Not sure why they do not do that already.. – NiCk Newman Jun 03 '15 at 17:18

2 Answers2

31

Seeing an increased memory footprint by a Node.js application is completely normal behaviour. Node.js constantly analyses your running code, generates optimised code, reverts to unoptimised code (if needed), etc. All this requires quite a lot of memory even for the most simple of applications (Node.js itself is from a large part written in JavaScript that follows the same optimisations/deoptimisations as your own code).

Additionally, a process may be granted more memory when it needs it, but many operating systems remove that allocated memory from the process only when they decide it is needed elsewhere (i.e. by another process). So an application can, in peaks, consume 1 GB of RAM, then garbage collection kicks in, usage drops to 500 MB, but the process may still keep the 1 GB.

Detecting presence of memory leaks

To properly analyse memory usage and memory leaks, you must use Node.js's process.memoryUsage().

You should set up an interval that dumps this memory usage into a file i.e. every second, then apply some "stress" on your application over several seconds (i.e. for web servers, issue several thousand requests). Then take a look at the results and see if the memory just keeps increasing or if it follows a steady pattern of increasing/decreasing.

Detecting source of memory leaks

The best tool for this is likely node-heapdump. You use it with the Chrome debugger.

  1. Start your application and apply initial stress (this is to generate optimised code and "warm-up" your application)
  2. While the app is idle, generate a heapdump
  3. Perform a single, additional operation (i.e. one more request) that you suspect will likely cause a memory leak - this is probably the trickiest part especially for large apps
  4. Generate another heapdump
  5. Load both heapdumps into Chrome debugger and compare them - if there is a memory leak, you will see that there are some objects that were allocated during that single request but were not released afterwards
  6. Inspect the object to determine where the leak occurs

I had the opportunity to investigate a reported memory leak in the Sails.js framework - you can see detailed description of the analysis (including pretty graphs, etc.) on this issue.

There is also a detailed article about working with heapdumps by StrongLoop - I suggest to have a look at it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Robert Rossmann
  • 11,931
  • 4
  • 42
  • 73
9

The garbage collector is not called all the time because it blocks your process. So V8 launches GC when it thinks it's necessary.

To find if you have a memory leak I propose to fire up the GC manually after every request just to see if your memory is still going up. Normally if you don't have a memory leak your memory should not increase. Because the GC will clean all non-used objects. If your memory is still going up after a GC call you have a memory leak.

To launch GC manually you can do that, but attention! Don't use this in production; this is just a way to cleanup your memory and see if you have a memory leak.

Launch Node.js like this:

node --expose-gc --always-compact test.js

It will expose the garbage collector and force it to be aggressive. Call this method to run the GC:

global.gc();

Call this method after each hit on your server and see if the GC clean the memory or not.

You can also do two heapdumps of your process before and after request to see the difference.

Don't use this in production or in your project. It is just a way to see if you have a memory leak or not.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sachacr
  • 704
  • 1
  • 9
  • 17
  • 2
    I think this is one of the worst advices possible: The problem is almost always not the GC being called "aggressively", but a memory leak in your code. Let V8 run its GC as it thinks it's necessary, and don't touch this. Make sure that your code is fine, and that it does not contain any memory leaks. Use statistics gathering and profiling to find out. There are so many things that are *way* better advice than "fire up GC manually". Hence -1ed. – Golo Roden Jun 03 '15 at 08:21
  • 5
    It was just an idea to make a diagnostic not to use in production. i should precise that was not a solution but a tool to see if you have a memory leak or not. – Sachacr Jun 03 '15 at 08:23
  • 1
    thank you Sachar, I think your contribution was very useful. – MFAL Oct 02 '15 at 14:25
  • i also think this is helpful to confirm a memory leakage. – RyanShao Aug 12 '22 at 03:09