[Node.js] - How to check and prevent nodejs(v8) server memoryleak
What are memory leaks
A memory leak is a condition that occurs when a program doesn't release the memory it allocates, i.e., step 3 of the lifecycle is not carried out. For instance, the system assigns memory locations to store values for the variables that we declare inside our program. In programming languages such as C/C++, we can allocate additional memory to hold the data and variables required. But, it's our responsibility to deallocate the memory after usage.
Memory Lifecycle
- Allocation of memory for the defined variable
- Manipulation operations such as read, write on the allocated memory
- After usage, releasing the allocated memory
Why Do Memory Leaks Happen in Node.js
Accidental Global Variables
Since the root node has reference to the global variables in JavaScript (i.e., global this or window), they are never garbage collected throughout the entire lifecycle of the application
let globalVal = 'Hello I`m global variable'
function foo() {
return globalVal;
}
---
function foo() {
globalVal = 'Hello I`m accidental global variable'
return globalVal
}
Closures
The process of accessing the parent function's variables inside an inner function is called closure.
function parentFunction() {
let a = parentVal
return function innerFunction(innerVal) {
return a;
}
}
the garbage collector will not reclaim the memory for the variable even though the parent function completes the execution
Timers
- setTimeout
- call after some delay
- setInterval
- call after some delay repeatly
let obj = {
runTimer: function() {
let ref = this;
setTimeout(()=> {
console.log('Timer call');
// runTimer function has setTimeout timer with an object reference
// So, every time it executes the callback, it gets re-initialized
ref.runTimer();
}, 5000)
}
}
obj.runTimer();
obj = null
console.log(obj)
Event Listeners
Javascript uses event listeners to handle events in DOM elements
Upstream Code
- memory leak can be due to upstream or remote code
- when you cannot determine the exact cause of the memory leak, checking the performance of its dependencies
V8 Garbage Collector
https://v8.dev/blog/trash-talk
The Garbage Collector (GC) traces the object reference from the root and marks all the nodes that are reachable from the root.
Algorithm
- Reference Counting
- check if an object has any reference to it
- if there are none marked garbage collectible
- check if an object has any reference to it
- Mark and Sweep
- check object is reachable from the root node in the memory
- more efficient than the reference counting
Minor GC
Scavenge
Major GC (Full Mark Compact)
Marking
- white: the initial state, this object has not yet been discovered
- gray: object has been discovered
- black: object and all of its neighbors discovered
- using reachability garbage collector check as a proxy for ‘liveness’
- gc follows every pointer recursively which reachable in the runtime
Sweeping
Remove all unused (white) objects.
- check gaps in memory left by dead objects and added to data structure called a free-list
- find contiguous gaps left by unreachable objects
- free-list are separated by the size of the memory chunk for quick look up
Compaction (Defragmenting)
Moves all marked — and thus alive — objects to the beginning of the memory region.
- based on a fragmentation heuristic choose to evacuate/compact some pages
- copy surviving objects into other pages that are not currently being compacted
- weakness of a garbage collector
- allocate a lot of long-living objects, pay a high cost to copy these objects
- to prevent this compact only some highly fragment pages
Essential tasks
- Identify live/dead objects
- Recycle/reuse the memory occupied by dead objects
- Compact/defragment memory (optional)
How to Detect Memory Leaks
https://nodejs.org/en/docs/guides/diagnostics/memory/#my-process-runs-out-of-memory
Scout APM
Scout APM is a monitoring tool that can trace resource usage and memory bloat. Getting started with Scout is as simple as installing a package.
const scout = require("@scout_apm/scout-apm");
scout.install({
allowShutdown: true, // allow shutting down spawned scout-agent processes from this program
monitor: true, // enable monitoring
name: "", // Name comes here
key: "" // Key comes here
});
const requests = new Map();
app.get("/", (req, res) => {
requests.set(req.id, req);
res.status(200).send("Hello World");
});
node-heapdump
Heapdump package dumps v8 heap for later inspection. It takes a memory snapshot using heapdump and helps to profile it for performance bottleneck and memory leak.
var heapdump = require("heapdump");
heapdump.writeSnapshot(function(err, filename){
console.log("Sample dump written to", filename);
});
node-inspector, chrome devTools …
How to fix the memory leak
you will realize fixing the memory leak is somewhat easier than diagnosing it.
Fixing Accidental Global variables
function foo() {
// javascript hoists it as a global variable
bar = 'This is global'
}
- use ‘use strict’
Use Global variables Effectively
- use less global variables
Use Closures Effectively
var newElem;
function parent() {
var someText = new Array(1000000);
var elem = newElem;
function child() {
if (elem) return someText;
}
return function () {};
}
setInterval(function () {
newElem = parent();
}, 5);
- Although elem is never used by parent function, it references ‘elem’
Debugging
- Determining how much space our specific type of objects
- What variables are preventing from being garbage collected
Can We Avoid Memory Leaks to Begin With?
- it is difficult to avoid it entirely at the beginning itself
- it is best to have ample observability of your application`s performance all the time
refer: https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156