【发布时间】:2021-02-08 22:11:42
【问题描述】:
我在一个项目中遇到了内存泄漏,我已经设法在一个简单的例子中重现了这个问题:
const { Readable } = require("stream");
for (let i=0; i<10000000; i++) {
const r = new Readable();
r.push(Buffer.from("a"));
if (i % 10000 === 0) {
const memory = process.memoryUsage();
console.log(memory.heapUsed, memory.heapTotal);
}
}
使用node --max-old-space-size=1024 test.js 运行,这样它就不会吃掉你所有的内存并始终如一地演示崩溃。
每次迭代都会泄漏内存,但我不知道为什么。销毁流似乎没有任何作用。我没有存储对数据的任何引用,所以 GC 应该在每个循环之后处理并清理它,但不是吗?
节点 12.18.3
更新: This 是我实际项目中存在问题的区域。我的项目是一个用于 RTS 游戏重播文件的解析器,旨在从重播中提取有意义的数据,然后将这些数据存储在数据库中。这不是时间关键,但我希望它相当快,所以它不会得到备份。有 1 或 2 次问题重播,这些游戏更长,包含更多数据,那时我注意到我的解析器有这个内存问题。
使用expose-gc 标志并调用global.gc() 似乎无济于事,但使其异步并时不时等待约10ms 的小延迟完全解决问题而不会耽搁太多,但是这感觉像是一个糟糕的解决方案。
const { Readable } = require("stream");
(async () => {
for (let i=0; i<10000000; i++) {
const r = new Readable();
r.push(Buffer.from("a"));
r.push(null);
if (i % 10000 === 0) {
const memory = process.memoryUsage();
console.log(memory.heapUsed, memory.heapTotal);
// global.gc(); // no luck
await delay(10);
}
}
})();
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => resolve(), ms);
});
}
【问题讨论】:
-
GC 通常不会运行,直到你的 JS 回到事件循环并且有一些空闲周期让 GC 可以完成它的工作。如果您处于紧密循环中,创建数百万个对象,那么在循环完成之前这些对象不会被 GC。如果您在循环中创建太多,kaboom 就会耗尽内存。每次循环迭代都没有 GC。事物符合 GC 条件,但在解释器有时间运行 GC 进程之前不会真正被释放。仅供参考,这不是泄漏。这是由您编写代码的方式引起的高峰值内存使用量。
-
@jfriend00 那有没有办法手动调用GC或者给节点喘息的空间来自动运行呢?
-
有。它需要一个命令行参数。见How to request the garbage collector in nodejs to run。通常不建议手动运行 GC,因为通常有更好的方法来设计代码以降低峰值内存使用量,因此不需要它。您尚未显示您的真实代码,因此我们无法对此提出建议。我还建议您阅读 this article 以了解有关 GC 的更多信息。
-
谢谢,我会做一些阅读和玩那个标志。简而言之,我的项目正在读取一个二进制文件并将其拆分为多个块,我在 while 循环中使用可读流处理每个块,直到它们都被读取并提取出有意义的数据。导致问题的实际部分与我的问题中的示例代码基本完全相同
-
例如,只取一个大字符串并在其上调用
.split()至少会使该数据使用的内存增加一倍甚至三倍,因为您不仅要为每条数据制作一个新副本在.split()结果中,但它创建的每个新对象都有一些开销。它都不会永久泄漏(因为它都会被 GCed),它只是更高的峰值内存使用量。然后,开始解析.split()数组中的每个结果,您可以非常轻松地再次使用两倍或三倍。