173

From the official documentation (source):

process.memoryUsage()

Returns an object describing the memory usage of the Node process measured in bytes.

var util = require('util');

console.log(util.inspect(process.memoryUsage()));

This will generate:

{ rss: 4935680, heapTotal: 1826816, heapUsed: 650472 }

heapTotal and heapUsed refer to V8's memory usage.

Exactly what do rss, heapTotal, and heapUsed stand for?

It might seem like a trivial question, but I've been looking and I could not find a clear answer so far.

Sender
  • 6,660
  • 12
  • 47
  • 66
Mahn
  • 16,261
  • 16
  • 62
  • 78

5 Answers5

253

In order to answer this question, one has to understand V8’s Memory Scheme first.

A running program is always represented through some space allocated in memory. This space is called Resident Set. V8 uses a scheme similar to the Java Virtual Machine and divides the memory into segments:

  • Code: the actual code being executed
  • Stack: contains all value types (primitives like integer or Boolean) with pointers referencing objects on the heap and pointers defining the control flow of the program
  • Heap: a memory segment dedicated to storing reference types like objects, strings and closures. enter image description here

Now it is easy to answer the question:

  • rss: Resident Set Size
  • heapTotal: Total Size of the Heap
  • heapUsed: Heap actually Used

Ref: http://apmblog.dynatrace.com/2015/11/04/understanding-garbage-collection-and-hunting-memory-leaks-in-node-js/

timqian
  • 3,106
  • 1
  • 14
  • 11
  • 70
    A picture can be worth 1000 words. – bmacnaughton Jul 08 '16 at 12:39
  • 17
    @bmacnaughton This one is worth 1013 words :) – alex Mar 27 '17 at 14:46
  • 4
    [rss, heapTotal, heapUsed] => size in megabytes? kilobytes? can you add that to your answer? are they all the same units? – Alexander Mills Mar 06 '18 at 01:13
  • How is heapTotal managed by node? In my app I see heapTotal going up steadily (regardless of GC) even though heapUsed remains bounded. I've not seen any explanation of how heapTotal is managed by node... I guess it's just reserved heap for future allocations, but is any of it ever released (if not being used)? What would cause it to stay high? – logidelic Sep 28 '18 at 14:15
  • 3
    there is a new property "external" on process.memoryUsage(), anyone know about that –  Jul 31 '19 at 06:34
  • 1
    Considering the question was about three values, this only explains the relationship between `rss` and heap, but does not explain the difference between `heapTotal` and `heapUsed`. IMO that is half the question here. – joniba Aug 12 '20 at 10:56
  • 2
    I am getting heap sizes > rss, how come? rss, 46.03MB heapTotal, 97.06MB heapUsed, 89.45MB – mlntdrv Jun 13 '22 at 14:09
42

RSS is the resident set size, the portion of the process's memory held in RAM (as opposed to the swap space or the part held in the filesystem).

The heap is the portion of memory from which newly allocated objects will come from (think of malloc in C, or new in JavaScript).

You can read more about the heap at Wikipedia.

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
  • Thanks, but the RSS is not actually used or allocated, right? am I right in my assumption that a more accurate definition would be the portion of memory that *could* be held in RAM? At least that seems to be what node.js is returning from what I can tell; rss is always equal to the entire RAM of the system even though I can see node.js is not using/allocating it. – Mahn Aug 19 '12 at 01:14
  • 4
    I don't think it's the total memory. On my machine the total memory is 8GB, but when I run a simple node process the RSS shows around 13MB, so I think it really shows how much memory is held in the RAM by this process. – Stefan Dec 02 '13 at 08:00
  • 1
    @Stefan right, I came across some sort of bug back then, but RSS seems to be reliable to me now. – Mahn Apr 03 '14 at 15:59
  • 4
    What's the difference between ``heapTotal`` and ``heapUsed``? – tiblu Nov 12 '15 at 12:03
  • 3
    @tiblu `heapTotal` is the total allocated heap space by the underlying V8 engine, for dynamic allocations. `heapUsed` is the memory used within that total space. Both are managed by V8, and are subject to grow/shrink whenever necessary. – elyas-bhy Dec 17 '15 at 10:55
19

The Node.js documentation describes it as follows:

heapTotal and heapUsed refer to V8's memory usage. external refers to the memory usage of C++ objects bound to JavaScript objects managed by V8. rss, Resident Set Size, is the amount of space occupied in the main memory device (that is a subset of the total allocated memory) for the process, which includes the heap, code segment and stack.

All mentioned values are expressed in bytes. So, if you just want to print them, you probably want to rescale them to MB:

const used = process.memoryUsage();
for (let key in used) {
  console.log(`Memory: ${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}

That will give you an output like:

Memory: rss 522.06 MB
Memory: heapTotal 447.3 MB
Memory: heapUsed 291.71 MB
Memory: external 0.13 MB
bvdb
  • 22,839
  • 10
  • 110
  • 123
3

Let's do this with an Example

The following example will show you how the increase in memory usage will actually increase the rss and heapTotal

const numeral = require('numeral');
let m = new Map();
for (let i = 0; i < 100000; i++) {
    m.set(i, i);
    if (i % 10000 === 0) { 
        const { rss, heapTotal } = process.memoryUsage();
        console.log( 'rss', numeral(rss).format('0.0 ib'), heapTotal, numeral(heapTotal).format('0.0 ib') )
    } 
}

Running The above will give you something like this:

rss 22.3 MiB 4734976 4.5 MiB
rss 24.2 MiB 6483968 6.2 MiB
rss 27.6 MiB 9580544 9.1 MiB
rss 27.6 MiB 9580544 9.1 MiB
rss 29.3 MiB 11419648 10.9 MiB
rss 29.3 MiB 11419648 10.9 MiB
rss 29.3 MiB 11419648 10.9 MiB
rss 32.8 MiB 15093760 14.4 MiB
rss 32.9 MiB 15093760 14.4 MiB
rss 32.9 MiB 15093760 14.4 MiB

This clearly shows you how using variable and continuously incrementing the space required by it increases the heapTotal and correspondingly the Resident Set Size(rss)

Cherag Verma
  • 299
  • 2
  • 9
3

RSS

RSS is a reasonable measure for the "total memory usage of the Node.js interpreter process". You simply be able to run your program if that goes above the available RAM. Note however that it excludes some types of memory, so the actual memory consumption on a server that just runs a single process could be higher (VSZ is the worst case).

The concept of RSS is defined in the Linux kernel itself as mentioned at: What is RSS and VSZ in Linux memory management and measures the total memory usage of the process. This value can therefore be measured by external programs such as ps without knowledge of Node.js internals, e.g. as shown at: Retrieve CPU usage and memory usage of a single process on Linux?

heapTotal and heapUsed

These are concepts internal to the Node.js implementation. It would be good to look at the v8 source code to understand them more precisely, notably I wonder if they just obtain those values from glibc with functions such as those mentioned at: API call to get current heap size of process? of if it has its own heap management done on top of it.

For the concept of heap in general see also: What and where are the stack and heap? and What is the function of the push / pop instructions used on registers in x86 assembly? The heap is overwhelmingly likely to take the majority of memory in a JavaScript program, I don't think you will ever bother to try and look for that memory elsewhere (besides perhaps typed arrays perhaps, which show separately under process.memoryUsage()).

Runnable test

The following code example can be used to do simple tests which I have tried to analyze at: https://cirosantilli.com/javascript-memory-usage-benchmark But unlike languages without garbage collection like C++, it is very difficult to predict why memory usage is so overblown sometimes, especially when we have smaller numbers of objects. I'm not sure other garbage collected languages do any better though.

You have to run the program with:

node --expose-gc main.js

main.js

#!/usr/bin/env node

// CLI arguments.
let arr = false
let array_buffer = false
let dealloc = false
let klass = false
let obj = false
let n = 1000000
let objn = 0
for (let i = 2; i < process.argv.length; i++) {
  switch (process.argv[i]) {
    case 'arr':
      arr = true
    break
    case 'array-buffer':
      array_buffer = true
    break
    case 'class':
      klass = true
    break
    case 'dealloc':
      dealloc = true
    break
    case 'obj':
      obj = true
    break
    case 'n':
      i++
      n = parseInt(process.argv[i], 10)
    break
    case 'objn':
      i++
      objn = parseInt(process.argv[i], 10)
    break
    default:
      console.error(`unknown option: ${process.argv[i]}`);
    break
  }
}

class MyClass {
  constructor(a, b) {
    this.a = a
    this.b = b
  }
}

let a
if (array_buffer) {
  a = new Int32Array(new ArrayBuffer(n * 4))
  for (let i = 0; i < n; i++) {
    a[i] = i
  }
} else if (obj) {
  a = []
  for (let i = 0; i < n; i++) {
    a.push({ a: i, b: -i })
  }
} else if (objn) {
  a = []
  for (let i = 0; i < n; i++) {
    const obj = {}
    for (let j = 0; j < objn; j++) {
      obj[String.fromCharCode(65 + j)] = i
    }
    a.push(obj)
  }
} else if (klass) {
  a = []
  for (let i = 0; i < n; i++) {
    a.push({ a: i, b: -i })
  }
} else if (klass) {
  a = []
  for (let i = 0; i < n; i++) {
    a.push(new MyClass(i, -i))
  }
} else if (arr) {
  a = []
  for (let i = 0; i < n; i++) {
    a.push([i, -i])
  }
} else {
  a = []
  for (let i = 0; i < n; i++) {
    a.push(i)
  }
}

if (dealloc) {
  a = undefined
}

let j
while (true) {
  if (!dealloc) {
    j = 0
    // The collector somehow removes a if we don't reference it here.
    for (let i = 0; i < n; i++) {
      if (obj || klass) {
        j += a[i].a + a[i].b
      } else if (objn) {
        const obj = a[i]
        for (let k = 0; k < objn; k++) {
          j += obj[String.fromCharCode(65 + k)]
        }
      } else if (arr) {
        j += a[i][0] + a[i][1]
      } else {
        j += a[i]
      }
    }
    console.error(j)
  }
  global.gc()
  console.error(process.memoryUsage())
}

Some things we learn on Node 16 Ubuntu 21.10:

  • with node --expose-gc bench_mem.js n 1 we see that the minimum RSS is 30 MiB and the minimum heapUsed 3.7 MB. RSS for a C hello world on the same system is 770 kB for comparison
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985