Node.js Hot Reload: Hidden Memory & Resource Leaks and How to Prevent Them
This article examines how Node.js hot‑module replacement works, why common tools like decache and clear‑module can still cause memory and resource leaks, demonstrates the problems with concrete code examples and heap snapshots, and offers practical guidance on when hot reload is safe to use.
Hot Reload Implementation Principle
Node.js loads modules by checking require.cache and, if absent, compiling the file and adding the module to both the cache and the parent module's children array. To reload a module without restarting the process, all references from the parent to the child must be removed, otherwise the old version remains in memory.
Problem 1: Memory Leak
Using decache only clears require.cache. The parent module’s children array still holds a reference to the old module, so each hot update adds another entry and the heap grows until OOM.
'use strict';
const cleanCache = require('decache');
let mod = require('./update_mod.js');
mod();
mod();
setInterval(() => {
cleanCache('./update_mod.js');
mod = require('./update_mod.js');
mod();
}, 100);The target module creates a large array to make the leak visible:
'use strict';
const array = new Array(1e5).fill('*');
let count = 0;
module.exports = () => {
console.log('update_mod', ++count, array.length);
};A memory‑printing helper shows rapid RSS and heap growth:
function printMemory() {
const { rss, heapUsed } = process.memoryUsage();
console.log(`rss: ${(rss/1024/1024).toFixed(2)}MB, heapUsed: ${(heapUsed/1024/1024).toFixed(2)}MB`);
}
printMemory();
setInterval(printMemory, 1000);Heap snapshots reveal that the children array of the original module accumulates duplicate entries for update_mod.js, confirming the leak.
Problem 2: Clear‑Module Improves but Still Leaks in Complex Scenarios
clear-moduleremoves both the cache entry and the parent’s reference, producing a wave‑shaped memory pattern that appears clean for the simple case.
const cleanCache = require('clear-module');When a second parent module ( utils.js) also requires the hot‑updated module, its own children array keeps a reference to each new version, causing the same leak despite clear-module handling the first parent correctly.
// utils.js
'use strict';
require('./update_mod.js');
setInterval(() => require('./update_mod.js'), 100);Running the combined scenario again leads to OOM, and heap snapshots show duplicate entries inside the utils.js module.
Problem 3: Resource Leak
Even when references are cleared, resources created inside the hot‑updated module (e.g., timers, sockets, file descriptors) are not automatically disposed. A timer defined inside update_mod.js continues to fire after the module is reloaded, demonstrating a classic resource leak.
// update_mod.js (resource‑leak example)
'use strict';
const start = new Date().toLocaleString();
setInterval(() => console.log(start), 1000);The timer persists after clear-module reloads the module, showing that hot reload alone cannot guarantee resource cleanup.
Problem 4: ESM Limitation
Both decache and clear-module operate on the CommonJS module system. Native ES modules (ESM) are loaded by the V8 engine and lack a public unload API, so current hot‑reload techniques cannot affect ESM code. Experimental ESM hooks exist but are still unstable.
Problem 5: Module Version Chaos
Because the cache can hold multiple versions of a module, different parts of the application may see different versions simultaneously. An example where update_mod.js exports a version string shows that after a hot update, the main entry sees the new version while a secondary module ( utils.js) continues to use the old one, leading to inconsistent behavior and potential memory waste.
// update_mod.js (version example)
'use strict';
const version = 'v1';
module.exports = () => version; // utils.js
'use strict';
const mod = require('./update_mod.js');
setInterval(() => console.log('utils', mod()), 1000); // index.js
'use strict';
const cleanCache = require('clear-module');
let mod = require('./update_mod.js');
require('./utils.js');
setInterval(() => {
cleanCache('./update_mod.js');
mod = require('./update_mod.js');
console.log('index', mod());
}, 1000);After updating update_mod.js to return 'v2', only the index output changes, while utils keeps printing v1.
Suitable Scenarios & Recommendations
Hot reload is valuable in development environments where rapid iteration outweighs occasional memory or resource leaks. In production, it should be limited to truly isolated modules that have a one‑to‑one parent‑child relationship and do not allocate long‑lived resources.
Future support for proper unload semantics in ESM could make hot reload safe for broader use.
References: Node.js internal module loader: https://github.com/nodejs/node/blob/v16.13.2/lib/internal/modules/cjs/loader.js#L176 decache source: https://github.com/dwyl/decache/blob/main/decache.js#L35 clear‑module source: https://github.com/sindresorhus/clear-module/blob/main/index.js#L25‑L31
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alipay Experience Technology
Exploring ultimate user experience and best engineering practices
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
