JavaScript 中的内存泄漏(WebWorker、Canvas、IndexedDB)

memory leak in JavaScript (WebWorker, Canvas, IndexedDB)

我需要一些帮助来查找小型浏览器/WebWorker 中的内存泄漏 JavaScript。我追踪到这段代码:

    /**
     * Resizes an Image
     *
     * @function scaleImage
     * @param {object}  oImageBlob  blob of image
     * @param {int}     iHeight     New height of Image
     * @return {ImageBitmap}    ImageBitmap Object
     */         
    async function scaleImage(oImageBlob, iHeight) {
        var img = await self.createImageBitmap(oImageBlob); 
        var iWidth = Math.round( ( img.width / img.height ) * iHeight); 
        var canvas = new OffscreenCanvas(iWidth,iHeight);
        var ctx = canvas.getContext('2d');  
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);      
        return(canvas.transferToImageBitmap());
    }

调用自:

    [inside a web worker: Some looping that calls this about 1200 times while parsind files from a s3 bucket ...]
         var oImageBlob = await new Response(oS3Object.Body, {}).blob();
         var oThumbnail = await scaleImage(oImageBlob, 100);
         await IDBputData(oInput.sFileKey, oImageBlob, oInput.sStore, oThumbnail)
    [... end of the loop]

另一个内部函数是

    /**
     * Put information to IndexedDB 
     *
     * @function IDBputData 
     * @param {string}  sKey        key of information
     * @param {string}  sValue      information to upload
     * @param {string}  sStore      name of object store
     * @param {object}  oThumbnail  optrional, default: null, thumbnail image
     * @return {object}     - SUCCESS: array, IndexedDB Identifyer "key"
     *                      - FAIL: Error Message
     */     
    async function IDBputData(sKey, sValue, sStore, oThumbnail=null) {
        var oGeneratedKeys = {};
        if(sStore=="panelStore"){
            oGeneratedKeys = await getKeyfromSKey(sKey);
        }
        return new Promise((resolve, reject) => {
            const tx = oConn.transaction(sStore, 'readwrite');                  
            const store = tx.objectStore(sStore);
            var request = {}
            request = store.put({panelkey: oGeneratedKeys.panelkey, layerkey: oGeneratedKeys.layerkey, countrycode: oGeneratedKeys.countrycode, value: sValue, LastModified: new Date(), oThumbnail: oThumbnail});
            request.onsuccess = () => (oThumbnail.close(),resolve(request.result));
            request.onerror = () => (oThumbnail.close(),reject(request.error));
        });
    }

当我在 Chrome 中以这种方式 运行 时,它会消耗我可用的所有 RAM(大约 8 GB),然后崩溃。 (具有 CPU/GPU 共享 RAM 的笔记本电脑)。

当我改变

         var oThumbnail = await scaleImage(oImageBlob, 100);

         var oThumbnail = null;

Chrome 的 RAM 消耗固定在 800 MB 左右,因此必须有一些具有最顶层功能“scaleImage”的东西。

我尝试调整它,但没有成功。

/**
 * Resizes an Image
 *
 * @function scaleImage
 * @param {object}  oImageBlob  blob of image
 * @param {int}     iHeight     New height of Image
 * @return {ImageBitmap}    ImageBitmap Object
 */         
async function scaleImage(oImageBlob, iHeight) {
    var img = await self.createImageBitmap(oImageBlob); 
    var iWidth = Math.round( ( img.width / img.height ) * iHeight); 
    var canvas = new OffscreenCanvas(iWidth,iHeight);
    var ctx = canvas.getContext('2d');  
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);      

    var oImageBitmap = canvas.transferToImageBitmap();

    ctx = null;
    canvas = null;
    iWidth = null;
    img = null;

    return(oImageBitmap);
}

非常感谢任何帮助。

要使 ImageBitmap 以最有效的方式释放其位图数据,您必须在完成后调用其 .close() 方法。

但实际上,您并不需要这个scaleImage功能。 createImageBitmap() 有一个 resizeHeight 选项,如果你在没有 resizeWidth 的情况下使用它,你将通过保持纵横比精确来调整图像大小,就像你在你的函数中所做的那样,除了它不需要两次分配位图。

调整大小后的 ImageBitmap 后,您可以将其传输到 BitmapRenderingContext(这将在内部 close() 原始 ImageBitmap)并从该渲染器调用 transferToBlob()。 这对您的计算机来说应该更轻。

async function worker_script() {
  const blob = await fetch( "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png" ).then( (resp) => resp.blob() );
  // resize from createImageBitmap directly
  const img = await createImageBitmap( blob , { resizeHeight: 100 } );
  const canvas = new OffscreenCanvas( img.width, img.height );
  canvas.getContext( "bitmaprenderer" )
    .transferFromImageBitmap( img ); // this internally closes the ImageBitmap
  const resized_blob = await canvas.convertToBlob();
  // putInIDB( resized_blob );
  // for the demo we pass that Blob to main, but one shouldn't have to do that
  // show the canvas instead ;)
  postMessage( { blob: resized_blob, width: img.width } );
  // to be safe, we can even resize our canvas to 0x0 to free its bitmap entirely
  canvas.width = canvas.height = 0;
}

// back to main
const worker = new Worker( getWorkerURL() );
worker.onmessage = ({ data: { blob, width } }) => {
  const img = new Image();
  img.src = URL.createObjectURL( blob );
  img.onload = (evt) => URL.revokeObjectURL( img.src );
  document.body.append( img );
  console.log( "ImageBitmap got detached?", width === 0 );
};

function getWorkerURL() {
  const content = "!" + worker_script.toString() + "();";
  const blob = new Blob( [ content ], { type: "text/javascript" } );
  return URL.createObjectURL( blob );
}