我很困惑为什么这个代码示例存在内存泄漏。
// ephemeralLeak.js
async function promiseValue(value) {
return value;
}
async function run() {
for (;;) {
await Promise.race([promiseValue("foo"), promiseValue("bar")]);
}
}
run();
我在 Node 20.10.0 中运行它,就像...
node --max-old-space-size=512 test/examples/ephemeralLeak.js
结果是...
<--- Last few GCs --->
[13958:0x6859c60] 5576 ms: Scavenge (reduce) 509.6 (519.9) -> 508.8 (519.9) MB, 15.20 / 0.00 ms (average mu = 0.285, current mu = 0.278) allocation failure;
[13958:0x6859c60] 5630 ms: Scavenge (reduce) 509.8 (520.2) -> 509.0 (520.4) MB, 10.20 / 0.00 ms (average mu = 0.285, current mu = 0.278) allocation failure;
[13958:0x6859c60] 5684 ms: Scavenge (reduce) 510.1 (520.4) -> 509.4 (520.7) MB, 4.11 / 0.00 ms (average mu = 0.285, current mu = 0.278) allocation failure;
<--- JS stacktrace --->
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
1: 0xc9e850 node::Abort() [node]
2: 0xb720ff [node]
3: 0xec1a70 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
4: 0xec1d57 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
5: 0x10d3dc5 [node]
6: 0x10d4354 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
7: 0x10eb244 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::internal::GarbageCollectionReason, char const*) [node]
8: 0x10eba5c v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
9: 0x10edbba v8::internal::Heap::HandleGCRequest() [node]
10: 0x1058fd7 v8::internal::StackGuard::HandleInterrupts() [node]
11: 0x14fa98a v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [node]
12: 0x1934ef6 [node]
Aborted (core dumped)
我试图将其写为一个没有内存泄漏的示例,同时说明一些更有问题的代码。但是,它也有内存泄漏,我不明白为什么。
我知道
Promise.race()
对每个承诺都会调用 then
。因此,如果您继续遵守相同的承诺,就会有越来越多的 then
订阅者订阅您仍然可以参考的承诺。这无法被垃圾收集并导致内存泄漏。
请参阅这个已知的 Javascript 错误/功能,了解长期承诺所遇到的问题... https://github.com/nodejs/node/issues/17469
但是在上面的代码中,所有的 Promise 都是短暂的。它们仅持续一次循环迭代,并且应该能够被垃圾收集,因为没有保留引用。
尽管如此,我总是以
JavaScript heap out of memory
结束
有人能明白为什么这会导致内存泄漏吗?
任何人都可以编写相同的代码而不会泄漏吗?
假设await的实现非常糟糕,以至于它永远不会“展开”已解决承诺的解析链,解决这个问题的唯一方法就是自己显式展开它。
在下面的示例中,这是通过调用(非异步)函数
run()
并提供显式回调来调用自身但没有异步/等待语法来实现的。这具有循环的效果,但没有内存泄漏和运行时失败。
async function promiseValue(value) {
return value;
}
async function doRace() {
await Promise.race([promiseValue("foo"), promiseValue("bar")]);
}
function run() {
doRace().then(() => setImmediate(run));
}
run();