Why Node.js vm Triggers OOM in V8: Hidden Compilation Cache Pitfalls
This article explains how frequent use of Node.js's vm module can cause out‑of‑memory crashes in V8 due to an uncollected compilation cache, the role of the --always‑promote‑young‑mc flag, and practical work‑arounds for Node.js 12, 14 and 16.
Talk is Cheap, Show Me the Code
First, a minimal reproducible script is presented to illustrate the problem.
Note: In an asynchronous while loop, manually call gc() at the end of each iteration.
Reproducing OOM
Running the script with Node.js 12 shows memory rising to about 100 MB and then dropping repeatedly, which is normal.
With Node.js 14 and 16 the same script crashes with an OOM error because V8’s bug (or feature) is triggered.
Note: Experiments were done on Node.js 14.16.0 and 16.8.0; later versions may fix the issue.
V8 Compilation Cache
Cache Technique
The V8 Compilation Cache is a low‑level hash table shared by the whole V8 isolate, using the source string as the key. It differs from the cachedData field of the vm API.
When the vm module compiles a script, it creates an UnboundScript object that looks up the cache using the source string. If a cache entry exists, it is returned; otherwise the script is compiled, the result is stored in the cache, and the source string becomes the cache key.
In real‑world pages this cache can achieve about 80 % hit rate and is fast because it lives in memory.
Cache GC Mechanism
The cache is only reclaimed by the CollectAllAvailableGarbage() GC pass, not by the regular CollectAllGarbage() pass. When the heap reaches its limit, V8 performs a “Last Resort” GC, which invokes CollectAllAvailableGarbage() to try to free the cache.
If the allocation still fails after this GC, the process crashes with a fatal error.
Temporary Fix
Disabling the compilation cache (e.g., with --no-compilation-cache) prevents the memory growth.
Old‑generation Allocation Failure Logic
V8’s allocation routine tries to allocate memory; on failure it runs a couple of normal GCs, then a “Last Resort” GC ( CollectAllAvailableGarbage()). If allocation still fails, an OOM error is thrown.
--always‑promote‑young‑mc Flag
In V8 v8.0.1 a new flag --always-promote-young-mc was introduced. When enabled, every Full GC promotes all young objects to the old generation, forcing an old‑generation allocation. If that allocation fails, V8 aborts with “MarkCompactCollector: young object promotion failed”, which matches the crash observed on Node.js 14/16.
"last resort gc" means that there was an allocation failure that a normal GC could not resolve.
--array‑buffer‑extension Flag Interaction
The --array-buffer-extension flag (read‑only, set at compile time) forces --always-promote-young-mc to be enabled. Node.js 14.5.0 turned this flag on, making it impossible to disable the promotion flag.
On versions prior to 14.5.0 the flag was off, allowing users to run Node.js with --no-always-promote-young-mc to avoid the OOM.
Summary
The vm module relies on UnboundScript, which uses V8’s Compilation Cache that is only cleared by CollectAllAvailableGarbage(), causing memory to keep growing.
Node.js 14/16’s V8 triggers an OOM after the heap limit is reached due to the promotion flag.
The cache can be disabled with --no-compilation-cache, but the promotion flag cannot be turned off because it is forced by --array-buffer-extension.
Solutions
Track the V8 bug/feature and follow its resolution.
Avoid frequent creation of vm contexts in performance‑critical code.
Easter Egg
When debugging the process with the Inspector, GC behavior changes and the OOM no longer occurs.
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.
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
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.
