Node.js使用Promise.all并行请求时遇JavaScript堆内存溢出求助
Node.js并行数据获取引发堆内存溢出问题排查与解决
问题背景
为提升程序速度,改用并行方式替代串行获取数据,核心代码如下:
function func() { var promises = []; for (let route of routes) { promises.push (calc(route, amount_wei)); } Promise.all(promises).then(function(completedItems) { completedItems.forEach(function(val) { var result_amount = val; if (result_amount[5] > amount_start * 1) { console.log("Good Trade"); } } }); }
仅循环10次(对应10个路由),却频繁触发JavaScript堆内存溢出,错误日志如下:
<--- Last few GCs ---> [23272:000001FDFC5C4DE0] 126463 ms: Mark-sweep (reduce) 9852.7 (10047.2) -> 9852.3 (10047.9) MB, 9776.7 / 0.0 ms (average mu = 0.350, current mu = 0.000) allocation failure scavenge might not succeed [23272:000001FDFC5C4DE0] 136795 ms: Mark-sweep (reduce) 9853.4 (10047.9) -> 9853.0 (10048.7) MB, 10314.7 / 0.0 ms (average mu = 0.201, current mu = 0.002) allocation failure scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory 1: 00007FF6487D7C4F v8::internal::CodeObjectRegistry::~CodeObjectRegistry+114207 2: 00007FF648765EC6 DSA_meth_get_flags+65542 3: 00007FF648766D7D node::OnFatalError+301 4: 00007FF64909B6CE v8::Isolate::ReportExternalAllocationLimitReached+94 5: 00007FF649085CAD v8::SharedArrayBuffer::Externalize+781 6: 00007FF648F2907C v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1468 7: 00007FF648F26194 v8::internal::Heap::CollectGarbage+4244 8: 00007FF648F23B10 v8::internal::Heap::AllocateExternalBackingStore+2000 9: 00007FF648F48696 v8::internal::Factory::NewFillerObject+214 10: 00007FF648C7AB65 v8::internal::DateCache::Weekday+1797 11: 00007FF6491293C1 v8::internal::SetupIsolateDelegate::SetupHeap+494417 12: 00007FF6490EB013 v8::internal::SetupIsolateDelegate::SetupHeap+239523 13: 000001FD8014DF37
已尝试的解决方法:
- 设置环境变量
NODE_OPTIONS为--max-old-space-size=8192 - 用命令
node --max-old-space-size=8192 index.js启动程序
当前堆统计信息:
{ total_heap_size: 55885824, total_heap_size_executable: 786432, total_physical_size: 55885824, total_available_size: 10505880728, used_heap_size: 29100784, heap_size_limit: 10536091648, malloced_memory: 794688, peak_malloced_memory: 4109184, does_zap_garbage: 0, number_of_native_contexts: 1, number_of_detached_contexts: 0 }
排查与解决方案
1. 定位内存泄漏根源:检查calc函数
从堆统计看,当前堆使用量远未达限制,但GC日志显示堆占用接近10GB,说明**calc函数内部可能存在内存泄漏或生成了超大数据对象**:
- 检查
calc是否在每次调用时创建大量未被回收的对象、缓存,或请求返回了远超预期的大体积数据 - 确认
calc是否存在循环引用,导致对象无法被GC回收 - 临时将
calc替换为返回固定值的模拟函数,验证是否还会触发内存溢出,以此排除并行逻辑本身的问题
2. 优化并行执行策略:控制并发量
即使只有10个请求,若每个calc都占用大量内存,同时执行仍可能压垮堆内存。可改为分批并行:
async function func() { const batchSize = 2; // 每次并行执行2个请求,可根据内存情况调整 for (let i = 0; i < routes.length; i += batchSize) { const batch = routes.slice(i, i + batchSize); const results = await Promise.all(batch.map(route => calc(route, amount_wei))); results.forEach(val => { if (val[5] > amount_start * 1) { console.log("Good Trade"); } }); // 显式释放引用,帮助GC快速回收 batch.length = 0; results.length = 0; } }
3. 强制GC与内存监控
在关键节点手动触发GC(仅用于调试,生产环境不建议),同时监控内存变化:
const v8 = require('v8'); async function func() { // ... 分批执行代码 ... // 每批执行完后触发GC if (global.gc) { global.gc(); console.log('GC executed'); console.log(v8.getHeapStatistics()); } } // 启动时需添加--expose-gc参数:node --expose-gc --max-old-space-size=8192 index.js
4. 排查外部资源泄漏
如果calc涉及数据库连接、文件流、网络请求等外部资源,需确认是否存在未正确关闭的资源:
- 检查是否有未释放的数据库连接池、WebSocket连接
- 确认请求是否设置了超时,避免长时间挂起导致资源堆积
5. 验证内存限制是否生效
有时--max-old-space-size参数可能未正确生效,可通过以下代码确认:
const v8 = require('v8'); console.log('Heap limit:', v8.getHeapStatistics().heap_size_limit / (1024 * 1024), 'MB');
若输出不是8192MB,说明参数未生效,需检查启动命令或环境变量是否正确设置(比如Windows环境下环境变量是否生效、是否有其他命令覆盖了参数)
内容的提问来源于stack exchange,提问作者Dani S
相关产品推荐
相关产品推荐

