You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux环境下Node.js内存持续增长问题的排查与解决咨询

内存泄漏排查与解决方案

问题描述

编写了如下代码:

async function saveIntoMp4(chunks) {
    const options = {
        type: "video/webm"
    }
    let blob = new Blob(chunks, options);
    chunks.length = 0 // to stop any memory leaks
    const buffer = Buffer.from(await blob.arrayBuffer());
    try {
        fs.writeFile(
            `./videos/1.mp4`,
            buffer,
            () => console.log("video is saved!")
        );
    } catch (error) {
        console.log(error)
    }
}

每次调用saveIntoMp4函数时,程序内存占用都会上升,甚至可达4GB。通过node --inspect跟踪堆内存,发现arrayBuffer已正常释放,但单次调用后内存从初始75MB涨到100MB以上。该问题在Ubuntu 20/22、CentOS 9及Rocky Linux(Red Hat衍生版)中均出现,仅Windows 11环境下无此问题。

分析与解决方案

1. 是否属于内存泄漏?

不一定是传统意义上的内存泄漏(即内存无法被GC回收)。更可能是Node.js在Linux平台的内存分配策略(如jemalloc分配器的行为)、代码中不必要的内存中转步骤,以及异步API使用方式导致的内存暂时堆积。如果多次调用后内存持续增长且无法回落,则可判定为泄漏;若空闲一段时间后内存能回落,则属于正常的内存分配延迟回收。

2. 核心优化点

(1)移除Blob中转,直接用Node.js原生Buffer处理

Blob是浏览器API,Node.js中对其的模拟实现会额外产生内存开销,且中转过程会复制数据,导致内存占用翻倍。直接用Buffer.concat()处理chunks数组即可:

const fs = require('fs').promises;

async function saveIntoMp4(chunks) {
    try {
        // 直接拼接chunks为Buffer,避免Blob中转
        const buffer = Buffer.concat(chunks);
        // 清空原chunks数组,切断外部引用
        chunks.length = 0;
        // 使用async/await版本的writeFile,确保写入完成后再释放buffer
        await fs.writeFile('./videos/1.mp4', buffer);
        console.log("video is saved!");
    } catch (error) {
        console.error(error);
    }
}

(2)改用流式写入,避免一次性创建大Buffer

如果视频文件较大,一次性拼接为大Buffer会瞬间占用大量内存。改用流式写入可以分块写入,内存占用更平稳:

const { createWriteStream } = require('fs');

async function saveIntoMp4(chunks) {
    const writeStream = createWriteStream('./videos/1.mp4');
    try {
        for (const chunk of chunks) {
            // 逐块写入
            if (!writeStream.write(chunk)) {
                // 当缓冲区满时,等待drain事件
                await new Promise(resolve => writeStream.once('drain', resolve));
            }
        }
        // 结束写入
        writeStream.end();
        // 等待写入完成
        await new Promise((resolve, reject) => {
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
        });
        console.log("video is saved!");
    } catch (error) {
        console.error(error);
        writeStream.destroy();
    } finally {
        chunks.length = 0;
    }
}

(3)修正异步写入的调用方式

原代码中fs.writeFile是异步回调版本,没有等待写入完成就结束函数。虽然理论上buffer会被GC标记,但如果频繁调用,可能导致多个buffer实例同时存在于内存中。改用fs.promises.writeFile并配合await,可以确保当前buffer在写入完成后才会被GC处理,避免内存堆积。

3. 额外排查点

  • 检查chunks数组是否被外部代码持有引用:如果外部还有其他变量引用该数组,chunks.length = 0无法完全释放内存,需确保没有额外引用。
  • 测试内存回落情况:调用函数后等待几分钟,观察内存是否下降。若能回落,则是GC时机问题(Linux下Node.js的GC触发阈值比Windows高),无需过度担忧;若持续增长,则需进一步排查是否有其他泄漏点(如事件监听器未移除、全局变量缓存等)。

内容的提问来源于stack exchange,提问作者Hypothesis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:53:14