【问题标题】:Releasing memory from blob creation / object URL in writing file to client's disk在将文件写入客户端磁盘时从 blob 创建/对象 URL 释放内存
【发布时间】:2018-06-06 16:46:58
【问题描述】:

更新

由于提出了以下问题并在发现代码中的错误后得出了一个更基本的问题,我发现了一些更多信息,例如在 MDN Web 文档中下载 API 方法 downloads.download() 它声明撤销对象 url 应该只在 文件/url 被下载之后执行。因此,我花了一些时间试图了解 Web 扩展是否使下载 API onChanged 事件对网页的 javascript“可用”,但我认为它不会。我不明白为什么下载 API 仅可用于扩展,尤其是当有很多关于同一内存使用/对象 URL 撤销问题的问题时。例如,Wait for user to finish downloading a blob in Javascript

如果你知道,请解释一下?谢谢。


从关闭 Firefox 浏览器开始,然后右键单击要在 Firefox 中打开的本地 html 文件,它会在 Windows 任务管理器中打开五个 firefox.exe 进程。其中四个进程的内存在 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 的时间吗?在撤销 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

【问题讨论】:

    标签: javascript memory-leaks blob revokeobjecturl


    【解决方案1】:

    我有点迷茫这里的问题是什么......

    但让我们尝试回答,至少部分回答:

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

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

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

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

    所以你看不到你的内存被立即释放是很正常的,根据经验,我会说 FF 不关心使用大量内存,当它可用时,不会让 GC 踢通常,whihc 有利于用户体验(GCing 通常会导致延迟)。


    对于您的下载问题,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});
    };
    &lt;a id="anchor" download="foo.txt"&gt;download&lt;/a&gt;

    【讨论】:

    • 感谢您的解释。关于你回复的后半部分,虽然我想我理解你,但我没有观察到无法下载 blob。如果 revoke 语句在锚点单击后立即出现,就像在 EDIT 下的链接中一样,下载仍然有效,但内存没有释放。就好像撤销被忽略了。只是为了测试,如果在下载完成后手动单击另一个按钮来调用撤销,那么在我一直在试验的小例子中,内存会在大约 30 秒内释放。再次感谢。
    • @Gary 实际上我想我说的是在“保存”提示之前发生了什么,此时浏览器应该已经完成​​了预取请求以了解另一端的内容.但是,我无法重现您的情况。你能提供一个演示这个问题的小提琴吗?请注意,我在 Mac 上,因此它可能是特定于操作系统的错误,但如果得到确认,那么您会发现内存泄漏并且浏览器开发人员通常喜欢修复它,因此打开问题将是一个不错的举措。 (但又一次,我无法在 mac 上重现)
    • 感谢您的澄清。由于将代码固定为 url = window.URL.createObjectURL(blob) 和 revokeObjectURL(url),内存通常在 1/2 - 2 分钟之间释放。如果在下载完成后通过单击按钮调用撤销,则内存不会更快释放。如果重复创建和下载 190MB 文件,内存使用量有时会增加,但最终会释放。即使 revoke 语句在文件下载之前运行,内存仍然被释放。我期待它发生得更快,并认为在代码更正后它仍然没有释放。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-31
    • 2012-08-04
    • 1970-01-01
    • 1970-01-01
    • 2017-10-19
    • 1970-01-01
    相关资源
    最近更新 更多