将文件写入客户端磁盘时从 blob 创建/对象 URL 中释放内存

Gar*_*ary 3 javascript memory-leaks blob revokeobjecturl

更新

由于提出了下面的问题并在发现代码中的错误后得出了一个更基本的问题,我发现了更多信息,例如在 MDN Web 文档中的下载 API 方法 downloads.download() 中,它指出对象的撤销url 仅应在文件/url 下载后执行。因此,我花了一些时间试图了解 Web 扩展是否使下载 API onChanged 事件对网页的 javascript“可用”,但我认为不会。我不明白为什么下载 API 仅适用于扩展程序,特别是当存在很多与内存使用/对象 url 撤销问题有关的问题时。例如,等待用户完成 JavaScript 中的 blob 下载

如果你知道的话请解释一下吗?谢谢。


从关闭的 Firefox 浏览器开始,右键单击要在 Firefox 中打开的本地 html 文件,它将打开并显示五个 firefox.exe 进程,如 Windows 任务管理器中所示。其中四个进程的启动内存介于 20,000k 到 25,000k 之间,一个进程的内存约为 115,000k。

此 html 页面有一个 indexedDB 数据库,其中有 50 个对象存储,每个存储包含 50 个对象。每个对象都从其对象存储中提取并使用 JSON.stringify 转换为字符串,然后写入二维数组。然后,数组的所有元素被连接成一个大字符串,转换为一个 blob,并通过 URL 对象写入硬盘,该对象随后立即被撤销。最终文件大约190MB。

如果代码在转换为 blob 之前停止,则 firefox.exe 进程之一的内存使用量会增加到大约 425,000k,然后在数组元素连接成一个数组后大约 5-10 秒内回落到 25,000k。单字符串。

如果代码运行完成,同一 firefox.exe 进程的内存使用量将增长到大约 1,000,000k,然后下降到大约 225,000k。以 115,000k 启动的 firefox.exe 进程也在代码的 blob 阶段增加到约 325,000k,并且从未减少。

将 blob 作为文本文件写入磁盘后,这两个 firefox.exe 进程永远不会释放大约 2 x 200,000k 增加的内存。

我已将每个函数中使用的每个变量设置为 null,并且除非刷新页面,否则内存永远不会释放。另外,这个过程是由按钮单击事件启动的;如果在没有中间刷新的情况下再次运行,这两个 firefox.exe 进程每次运行都会额外占用 200,000k 内存。

我一直无法弄清楚如何释放内存?

这两个函数非常简单。json[i][j] 保存数据库中第 i 个对象存储中第 j 个对象的字符串版本。os_data[] 是一个小对象数组 { "name" : objectStoreName, "count" : n },其中 n 是存储中的对象数量。如果未调用 write_to_disk,则 build_text 函数似乎会释放内存。因此,问题似乎与 blob 或 url 有关。

我可能忽略了一些显而易见的事情。感谢您提供的任何指导。

编辑:

我从JavaScript: Create and save file 中看到,我在 revokeObjectURL(blob) 语句中存在错误。它无法撤销 blob,需要将 createObjectURL(blob) 保存到像 url 这样的变量中,然后撤销 url,而不是 blob。

这在大多数情况下都有效,并且在大多数情况下,内存都是从上面提到的两个 firefox.exe 进程中释放的。这给我留下了一个关于撤销网址的时间的小问题。

如果撤销是为了释放内存,那么只有在文件成功下载后才应该撤销 url 吗?如果撤销发生在用户单击“确定”下载文件之前,会发生什么情况?假设我点击按钮从数据库中准备文件,准备好后浏览器会弹出下载窗口,但我等了一会儿考虑如何命名文件或将其保存在哪里,撤销语句不会已经运行但 url 仍然被浏览器“保留”,因为它将下载什么?我知道我仍然可以下载该文件,但是撤销是否仍会释放内存?从我对这个例子的少量实验来看,它似乎在这种情况下没有被发布。

如果在文件成功或失败下载到客户端时触发一个事件,那么这不是应该撤销 url 的时间吗?最好在撤销 URL 之前设置几分钟的超时,因为我很确定没有事件表明客户端下载已结束。

我可能不明白一些基本的东西。谢谢。

function build_text() {

    var i, j, l, txt = "";

    for ( i = 1; i <=50; i++ ) {

         l = os_data[i-1].count;

         for  ( j = 1; j <= l; j++ ) {

              txt += json[i][j] + '\n';

         }; // next j

    }; // next i


    write_to_disk('indexedDB portfolio', txt); 

    txt = json = null;

} // close build_text




function write_to_disk( fileName, data ) {  

    fileName = fileName.replace(".",""); 

    var blob = new Blob( [data], { type: 'text/csv' } ), elem;  


    if ( window.navigator.msSaveOrOpenBlob ) {

         window.navigator.msSaveBlob(blob, fileName);

    } else {

        elem = window.document.createElement('a');

        elem.href = window.URL.createObjectURL(blob);

        elem.download = fileName;        

        document.body.appendChild(elem);

        elem.click();        

        document.body.removeChild(elem);

        window.URL.revokeObjectURL(blob);

   }; // end if


   data = blob = elem = fileName = null;


} // close write_to_disk
Run Code Online (Sandbox Code Playgroud)

Kai*_*ido 10

我有点不知道这里的问题是什么......

但让我们试着回答一下,至少回答一部分:

首先,让我们解释一下URL.createObjectURL(blob)大致的作用:

它创建一个 Blob URI,该 URI 指向blob内存中的 Blob,就像它位于可访问的位置(如服务器)一样。
只要该 blob URI未被撤销,它就会被标记blob为不可被垃圾收集器 ( GCblob ) 收集,这样您就不必在脚本中维护对它的实时引用,但您仍然可以使用/加载它。

URL.revokeObjectURL然后将断开 blob URI 和内存中的 Blob 之间的链接。它不会直接释放占用的内存blob,它只会删除自己对GC的保护,[并且不会再指向任何地方]。
因此,如果有多个 Blob URI 指向同一个 Blob 对象,则仅撤销一个 Blob URI 不会破坏其他 Blob URI。

现在,只有当 GC 启动时,内存才会被释放,而这仅由浏览器内部决定,当它认为这是最好的时间,或者当它看到它没有其他选择时(通常是当它错过内存空间时) )。

所以你没有看到你的内存被立即释放是很正常的,根据经验,我想说 FF 不关心使用大量内存,当内存可用时,使得 GC 不那么频繁地启动,这有利于用户体验(GC 通常会导致滞后)。


对于您的下载问题,实际上,Web API 没有提供一种方法来了解下载是否成功或失败,即使它刚刚结束。
对于撤销部分,这实际上取决于您何时执行。
如果您直接在点击处理程序中执行此操作,则浏览器尚未完成预取请求,因此当发生点击的默认操作(下载)时,不会有任何由 URI 链接的内容不再了。
现在,如果您在“保存”提示后确实撤销了 Blob URI,则浏览器将完成预取请求,因此可能能够自行标记不应清除 Blob 资源。但我不认为这种行为与任何规范相关,最好至少等待窗口事focus​​件,此时资源的下载应该已经开始。

const blob = new Blob(['bar']);
const uri = URL.createObjectURL(blob);
anchor.href = uri;
anchor.onclick = e => {
  window.addEventListener('focus', e=>{
    URL.revokeObjectURL(uri);
    console.log("Blob URI revoked, you won't be able to download it anymore");
  }, {once: true});
};
Run Code Online (Sandbox Code Playgroud)
<a id="anchor" download="foo.txt">download</a>
Run Code Online (Sandbox Code Playgroud)