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

Browser/WebWorker下结合Canvas、IndexedDB的JavaScript内存泄漏排查求助

WebWorker 图片处理内存泄漏排查与修复

已定位的问题特征

  • 泄漏触发链路:WebWorker 内循环处理约1200张S3桶内图片,每次调用scaleImage生成100px高度的缩略图,传入IndexedDB完成存储
  • 现象对照:若将scaleImage返回的缩略图直接置为null,Chrome内存可稳定维持在800MB;开启缩略图生成逻辑后,会快速耗尽设备8GB的CPU/GPU共享内存并崩溃
  • 已验证无效的尝试:在scaleImage函数末尾将所有内部局部变量(img、ctx、canvas、iWidth)手动置空,未解决内存持续上涨的问题

泄漏核心根源

  1. 源图ImageBitmap未主动释放
    • ImageBitmap 是直接占用GPU共享内存的原生资源,不受JS垃圾回收的即时管控,必须主动调用其close()方法才能立刻释放显存占用,仅手动将引用变量置空无法触发资源回收
    • 原逻辑中createImageBitmap创建的原图对象,从创建到函数执行结束都没有调用close(),每次循环的原图Bitmap都会持续驻留共享内存,1200次循环累积的原图内存远大于缩略图的内存占用
  2. IndexedDB写入的缩略图释放逻辑存在兜底缺失
    • 原IDBputData仅在request.onsuccess/request.onerror回调中释放缩略图,如果IndexedDB事务中途中断、连接异常断开,这两个回调不会触发,导致缩略图Bitmap永远驻留内存
    • 未覆盖事务层面的异常、中止场景,原生资源的释放逻辑没有兜底保障

修复后可直接使用的代码

调整后的scaleImage函数

async function scaleImage(oImageBlob, iHeight) {
    let sourceImg = null;
    try {
        sourceImg = await self.createImageBitmap(oImageBlob); 
        const targetWidth = Math.round((sourceImg.width / sourceImg.height) * iHeight);
        const canvas = new OffscreenCanvas(targetWidth, iHeight);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(sourceImg, 0, 0, canvas.width, canvas.height);
        // 图像绘制完成后第一时间释放源图,不等函数返回
        sourceImg.close();
        sourceImg = null;
        return canvas.transferToImageBitmap();
    } catch (err) {
        // 异常分支兜底释放已创建的源图
        if (sourceImg) sourceImg.close();
        throw err;
    }
}

调整后的IDBputData函数

async function IDBputData(sKey, sValue, sStore, oThumbnail = null) {
    let oGeneratedKeys = {};
    if(sStore === "panelStore"){
        oGeneratedKeys = await getKeyfromSKey(sKey);
    }
    // 通用资源释放逻辑,避免重复代码
    const releaseThumbnail = () => {
        if (oThumbnail) {
            try { oThumbnail.close(); } catch (e) {}
            oThumbnail = null;
        }
    }
    return new Promise((resolve, reject) => {
        const tx = oConn.transaction(sStore, 'readwrite');                  
        const store = tx.objectStore(sStore);
        const storeData = {
            panelkey: oGeneratedKeys.panelkey,
            layerkey: oGeneratedKeys.layerkey,
            countrycode: oGeneratedKeys.countrycode,
            value: sValue,
            LastModified: new Date(),
            oThumbnail: oThumbnail
        };
        const request = store.put(storeData);
        request.onsuccess = () => {
            releaseThumbnail();
            resolve(request.result);
        };
        request.onerror = () => {
            releaseThumbnail();
            reject(request.error);
        };
        // 补全事务层级的异常、中止兜底,防止回调不触发导致资源泄漏
        tx.onerror = () => {
            releaseThumbnail();
            reject(tx.error);
        }
        tx.onabort = () => {
            releaseThumbnail();
            reject(tx.error);
        }
    });
}

额外优化建议

  • 避免单流程串行等待:原逻辑是处理完一张图、等IndexedDB写入完成后再处理下一张,可设置最多3-5个并发的图片处理流程,减少GPU内存排队等待的累积
  • 跳过不必要的Blob拷贝:可直接将S3返回的Body流传给createImageBitmap,跳过new Response(oS3Object.Body).blob()的中间层Blob拷贝,减少堆内存占用
  • 所有原生图形资源(ImageBitmap、OffscreenCanvas)必须在明确的生命周期节点调用close()方法,依赖JS垃圾回收会导致GPU共享内存释放滞后,长时间运行必然触发内存耗尽崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.25 08:06:06