node memory management - GradedJestRisk/js-training GitHub Wiki

Table of Contents

General

Fromm rollout.io

Node.js applications are long-running processes. While this has lots of positive implications such as allowing database connections to be set up once and then reused for all requests, this may also cause problems. (..) V8 compiles JavaScript down to native code and executes it. During execution, it manages the allocation and freeing of memory as needed. This means that if we talk about memory management in Node.js we actually always talk about V8.

Node memory space (Resident Set - RS) are :

  • code: source code (ie. strings, see mmap)
  • stack: "function call" stack (control flow), local variables + primitive value (integer, string, boolean)
  • heap: dynamic allocations (strings, arrays, objects, closure stack frame )
    • new (younger generation) - first instanciation - 1 to 8 Mb - fast to allocate, fast to GC (Scavenge)
    • old (older generation) - those whose survived 2 GC in ew - fast to allocate, slow to GC (Mark & sweep)
To get an idea of actual used memory by the node process, check the Resident Set Size (RSS)
  • the code itself
  • the stack
  • the heap
To get memory usage: process.memoryUsage()
  • RS
  • heap
    • used
    • allocated (labelled "total")
Without memory leak, with short Scavenge and long Mark&Sweep cycles

If a program allocates memory that is never freed, the heap will constantly grow until the usable memory is exhausted, causing the program to crash. This s called a memory leak.

When a memory leak: RSS (total allocated memory, resident set size) keep rising

Log memory usage

const loggingIntervalMilliseconds = 10;

const logMemory = function() {
  const now = dayjs().format(timeFormat);
  console.log(`${now};${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}`);
};

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

const logMemory = function() {
  const now = dayjs().format(timeFormat);
  console.log(`${now};${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}`);
};

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function foo(){

  console.log('time;heap_mb');
  console.log('enter foo');
  logMemory();

  const memoryTimer = setInterval(logMemory, loggingIntervalMilliseconds);

  console.log('enter foo');

  // this is the code you want to check memory use
  // replace by actual code
  await sleep(200);

  console.log('leave foo');

  // do stuff

  console.log('leave foo');
  clearInterval(memoryTimer);
}

Quota

Built-in limit: 1.5 Gb

Limit allocatable memory using --max-old-space-size parameter (Mb):

  • environment variable: NODE_OPTIONS="--max-old-space-size=5000" node
  • parameter: node --max-old-space-size=5000
To test in one-liner, create many arrays:
node --max-heap-size=5000 -e "let arrays = []; while (true){ arrays.push(new Array(1000000));}"

Exhaust memory

Exhaust exponentially using integer array Steps:

  • exhaust until default (usually, 1,5Gb): node exhaust.js
  • exhaust until specified (here, 3 Gb) : node --max-old-space-size=3000 exhaust.js

GC strategies

Deep dive

Memory leak

A tour

Historic

Closure (Meteor)
var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  // Define a closure that references originalThing but doesn't ever
  // actually get called. But because this closure exists,
  // originalThing will be in the lexical environment for all
  // closures defined in replaceThing, instead of being optimized
  // out of it. If you remove this function, there is no leak.
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    // While originalThing is theoretically accessible by this
    // function, it obviously doesn't use it. But because
    // originalThing is part of the lexical environment, someMethod
    // will hold a reference to originalThing, and so even though we
    // are replacing theThing with something that has no effective
    // way to reference the old value of theThing, the old value
    // will never get cleaned up!
    someMethod: function () {}
  };
  // If you add `originalThing = null` here, there is no leak.
};
setInterval(replaceThing, 1000);

Estimate used memory by code

Disable GC, compare before and after

node --expose-gc --trace-gc
--experimental-repl-await
--max-old-space-size=1024 --initial-old-space-size=512
--min-semi-space-size=2048 --max-semi-space-size=2048
--no-idle-time-scavenge
--no-incremental-marking
--no-page-promotion
--nologfile-per-isolate
-e 'require("./bin/www")'
-i
⚠️ **GitHub.com Fallback** ⚠️