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

JavaScript向Web Worker传可转移ArrayBuffer时GC未触发内存泄漏问题

问题根因

你遇到的内存持续上涨问题本质不是GC失效,而是可转移对象的引用未被彻底断开,或是高频内存分配下GC调度滞后,和JS不支持手动触发GC没有直接关系。
可转移对象转移的仅为ArrayBuffer的所有权:主线程侧执行转移后,对应buffer会立刻被分离(detach)无法访问,但Worker侧只要存在任何一条指向该buffer的强引用,GC就不会回收这块内存。仅给接收消息的参数赋值为null无法覆盖所有隐式引用场景,是这类问题最常见的误区。
常见的未断开引用场景包括:

  • Worker侧将接收到的buffer/对应TypedArray存在全局变量、长生命周期缓存队列、Map/Set中,处理完帧后未清理对应引用
  • 开发环境下使用console.log打印帧对象或buffer:Chrome DevTools的Console会对打印的对象保持强引用直到控制台被清空,会造成开发环境下的假内存泄漏
  • 基于传入buffer创建的TypedArray视图、DataView等对象未解除引用:这类视图和底层buffer是强绑定关系,视图不回收,buffer也无法被释放
  • 帧处理速度跟不上帧生产速度,未处理的帧在Worker侧队列中持续堆积,造成内存线性上涨
修复方案

1. 先清理所有隐式引用

  • 排查Worker侧所有可能持有帧buffer的位置,全局变量、模块级缓存、闭包引用的帧对象处理完后必须全部解除引用,不要仅删除数组索引,要确保队列中没有残留的帧对象引用
  • 生产环境删除所有打印帧、buffer对象的console语句,调试时如果需要打log,只打印帧的尺寸、时间戳等基础元数据,不要打印整个对象
  • 如果你在Worker中基于传入buffer创建了新的TypedArray/DataView视图,处理完帧后不要保留这些视图的引用,让其随局部作用域自动回收即可,不需要手动赋值null

2. 采用缓冲池复用方案(推荐,音视频高频处理场景标准解法)

不要每帧新建Float32Array再转移,这种模式每秒会产生几十次内存分配,很容易出现GC调度不及时的问题。通过预分配固定数量的缓冲、在主线程和Worker间循环转移复用,可以从根源上避免频繁内存分配和回收:
核心逻辑是初始化时只分配固定数量的帧缓冲,Worker处理完一帧后立刻将缓冲转移回主线程用于写入下一帧,整个运行过程中没有新的内存分配,完全规避GC问题。
主线程示例代码:

// 按实际单帧大小计算缓冲长度,例:1080P RGBA帧对应长度
const FRAME_BUFFER_LENGTH = 1920 * 1080 * 4;
// 预分配3-4个缓冲足够覆盖生产消费的速度差,避免队列堆积
const bufferPool = [];
for (let i = 0; i < 3; i++) {
  bufferPool.push(new Float32Array(FRAME_BUFFER_LENGTH));
}

// 接收Worker回传的已处理完的缓冲
this.worker.onmessage = (e) => {
  if (e.data.command === 'RecycleBuffer') {
    bufferPool.push(e.data.buffer);
    writeNextFrame();
  }
};

function writeNextFrame() {
  if (!bufferPool.length) return;
  const frameBuffer = bufferPool.pop();
  // 在这里将当前帧的原始数据写入frameBuffer
  // ... 帧写入逻辑
  this.worker.postMessage({
    command: 'SetVideoBuffer',
    data: { videoFrame: frameBuffer }
  }, [frameBuffer.buffer]);
}

Worker侧示例代码:

self.onmessage = async (e) => {
  if (e.data.command === 'SetVideoBuffer') {
    const frame = e.data.data.videoFrame;
    // 在这里执行视频帧处理逻辑
    // ... 视频处理
    // 处理完成后立刻将缓冲转移回主线程复用,不要保留任何引用
    self.postMessage({
      command: 'RecycleBuffer',
      buffer: frame
    }, [frame.buffer]);
  }
};

3. 排查Chrome特有问题

这个场景确实存在Chrome专属的已知问题:

  • 110版本之前的Chrome存在WebCodecs相关的可转移ArrayBuffer回收bug,如果你是从VideoFrame.copyTo()获取的buffer,升级到最新版Chrome即可解决
  • Chrome Memory面板录制堆快照时会临时保留对象引用,不要通过单次堆快照判断内存泄漏:打开Chrome任务管理器(Shift+Esc)观察页面内存,连续播放5-10分钟视频,如果内存稳定在固定区间不持续线性上涨,就不存在真实泄漏。
验证方法

使用Chrome DevTools的Memory面板,选择「Allocation instrumentation on timeline」录制30秒左右的帧处理流程:

  • 如果修复前存在持续的ArrayBuffer分配、且分配后没有被回收的标记,说明存在真实泄漏
  • 如果采用缓冲池方案后,仅初始化阶段有固定数量的ArrayBuffer分配,运行过程中没有新的同类型buffer分配,说明问题已经解决

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:33:29