Memory leaks in NodeJS application?!

Gaspar Nagy
5 min readSep 25, 2019

--

What can cause a memory leak?

Global Variables

They stay in memory until the application stops. That’s why it is dangerous to set a big amount of data in them.

Multiple References

This is also dangerous because you can end up with a lot of references to the same object, which could not be garbage collected and it will increase your heap size.

Closures

It has similar problem like the multiple references. You are using closure to keep references on some objects to use them later.

Forgotten timers or callbacks

Using setInterval() with callback keeps the reference to that callback until that timer is not unregistered.

V8’s memory

Resident set

The size of Resident set represents the total memory allocated for the process execution. It includes all the code, stack and heap.

  • code segment — the actual code being executed
  • stack — contains local variables with primitive types (integers, booleans etc.), pointers referencing to objects in heap and some other pointers which defines the flow of the program
  • heap — all the reference types like objects, strings or closure are stored here
  • used heap — the heap which is actually used during the execution

Heap organisation

As you can se the V8's heap is organised into two generations with different spaces.

Young generation

  • the size is around 1 to 8 MBs
  • garbage collection is fast
  • all the new allocations are happening here

New space

It is small, independent of other spaces and is designed to be garbage collected very quickly. Most objects starts and end their lives here.

Old Generation

  • allocation is fast
  • collection is expensive so it is not performed too frequently
  • all objects which survived the garbage collection in New space get moved here
  • the maximum size is 1.4GB

Old space

Objects which survives the New space garbage collection gets promoted here. Contains Objects which may have pointers to other objects or which just contain raw data (no pointers to other objects) are stored in this space.

Large Object space

This space contains objects which are larger than the size limits of other spaces. Large objects are never moved by the garbage collector.

Code space

Executable code compiled by V8 is allocated here. This is the only space with executable memory.

Map space

Contains Objects which are all the same size and points to metadata of hidden classes, which simplifies collections.

Garbage collection

Garbage collection is the process of reclaiming the memory occupied by objects that are no longer in use by the application. An object can be garbage collected if it is unreachable from root node (no reference is pointing to it).

Garbage collection should be done periodically with the following tasks:

  1. Identify live/dead objects
  2. Recycle/reuse the memory occupied by dead objects
  3. Compact/defragment memory (optional)

In the described generations two main garbage collection algorithms are running periodically — Scavenge and Mark/Sweep/Compact.

Scavenge

Scavenge is fast and can run many times. It cleans up a small amount of memory.

Mark/Sweep/Compact

Mark/Sweep/Compact is more complex and takes longer to accomplish. It has three steps:

  1. Mark the unreachable objects
  2. Sweep the memory to clean these objects
  3. Compact memory to optimise it

Further reading about how these algorithms works: https://v8.dev/blog/trash-talk.

Monitoring tools

When we are talking about memory leak detection it is more than needed to have a basic monitoring tool. Here comes Grafana and Prometheus on the table. Prometheus is for collecting monitoring data and Grafana is for visualise these data.

The main setup is to create a Prometheus server which is scraping data from your application and also installing Grafana for querying these data (https://prometheus.io/docs/visualization/grafana).

To implement Prometheus to your nodeJS application you can use the prom-client package from npm. Also there is the prometheus-gc-stats package which helps you automatically report garbage collection stats.

In express application with a custom counter it can look like this:

Some of the default data you may collect:

  • garbage collector pause (nodejs_gc_pause_seconds_total)
  • garbage collector runs per second (nodejs_gc_runs_total)
  • garbage collector reclaimed bytes (nodejs_gc_reclaimed_bytes_total)
  • heap size (nodejs_heap_size_total_bytes)
  • heap space size (nodejs_heap_space_size_total_bytes)
  • used heap space size (nodejs_heap_space_size_used_bytes)
  • external memory usage (nodejs_external_memory_bytes)
  • process resident memory usage (process_resident_memory_bytes)
  • cpu usage (process_cpu_seconds_total)
  • total active requests (nodejs_active_requests_total)
  • total active handlers (nodejs_active_handles_total)
  • event loop lag duration (nodejs_eventloop_lag_seconds)

When everything is set than it looks like this:

nodejs_heap_space_size_total_bytes and nodejs_heap_space_size_used_bytes
nodejs_external_memory_bytes and nodejs_gc_runs_total
process_resident_memory_bytes and nodejs_eventloop_lag_seconds

Inspecting the NodeJS process

If you are can restart the NodeJS process you can simply do:

node --inspect index.js

Running process

From the docs https://nodejs.org/en/docs/guides/debugging-getting-started/

Node.js will also start listening for debugging messages if it receives a SIGUSR1 signal. (SIGUSR1 is not available on Windows.) In Node.js 7 and earlier, this activates the legacy Debugger API. In Node.js 8 and later, it will activate the Inspector API.

So for Unix like environments you can do the following:

kill -s SIGUSR1 PID
# or
kill -USR1 PID

If you are a Windows user, you need to start another process in new command line with the following:

node -e "process._debugProcess(PID)"

In both environments this will signal to the NodeJS process to start the inspecting which means that it starts listen for a debugging client on a port 9229.

Chrome Dev tools

Your process is already listening for debugging client so open your Chrome on and type chrome://inspect:

On this page you need to click to Open dedicated DecTools for Node:

By this your DevTools for NodeJS should open.

In my next Article I am going to describe how to use the Profiler and the Memory tabs.

Thanks a lot for reading!

--

--

Gaspar Nagy

Leading my own software development company https://techmates.io. We focus on long term partnerships with our clients where we overtake part of the development.