node memory management - GradedJestRisk/js-training GitHub Wiki
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)
- the code itself
- the stack
- the heap
process.memoryUsage()
- RS
- heap
- used
- allocated (labelled "total")
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
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); }
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
node --max-heap-size=5000 -e "let arrays = []; while (true){ arrays.push(new Array(1000000));}"
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
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);
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