1

I use revokeObjectURL to prevent memory leak.

This is sample code for my question.

function App(){
const [urls, setUrls] = React.useState([]);
  const handleUpload = (e) => {
    const file = e.target.files[0];
    const blobUrl = window.URL.createObjectURL(file);
    setUrls((prev) => prev.concat(blobUrl));
  };
  return (
    <div className="App">
      <input type="file" onChange={handleUpload} />
      <div>
        {urls.map((url) => (
          <p key={url}>
            <a href={url} target="_blank">
              {url}
              <img src={url} width={25} />
            </a>
            <button onClick={() => window.URL.revokeObjectURL(url)}>
              Revoke
            </button>
          </p>
        ))}
      </div>
    </div>
  );
}

ReactDOM.render(
  <App />, document.getElementById('root')
)
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

When I upload image, I can create the image file to blob url with createObjectURL()

I worry about the memory leak, so I make a button for revokeObjectURL().

After click the revoke button, I expected image doesn't show anymore.

Is it still resolved about memory leak if I load the image and after revoke url?

I wonder it because the image is still showing.

kyun
  • 9,710
  • 9
  • 31
  • 66

1 Answers1

3

You have to understand that createObjectURL(blob) will return a new URL that points to some place in the browser's memory where the Blob can be accessed in order to be fetched by various APIs.

Calling revokeObjectURL() will break this link, and thus navigating, fetching, loading this URL will all fail after this call.

This is usually important memory wise because as long as this link is "alive", the browser doesn't know if it can release its link to the Blob's data it has in memory. The returned URL is just a string, it can't know what you did with it, and unlike other pointers we have in JS, its Garbage Collector can't know if something is still going to use it or not.
By calling revokeObjectURL() you let the Garbage Collector know that the Blob isn't linked to that URL anymore and that if nothing else holds a pointer to it, it can collect it.

However, the APIs that already did consume this URL have now access to another object that the Blob. For instance the <img> element isn't constantly fetching the URL to fetch the resource at the other end. If it did so, network traffic would be huge. Instead, it fetches it once, stores the result in memory and then keeps that. (Note that for a blob: URL the result should probably not be stored in cache, though there is an interop issue here, Firefox does cache it somehow, Chrome doesn't).

const img = document.querySelector("img");
document.createElement("canvas").toBlob(blob => {
  const url = URL.createObjectURL(blob);
  img.src = url;
  img.onload = (evt) => {
    img.onload = null
    URL.revokeObjectURL(url);
    img.src = "";
    setTimeout(() => img.src = url);
    const img2 = new Image();
    img2.src = url;
    document.body.append(img2);
  }

})
img {
  border: 1px solid;
}
<img>

In Chrome the above snippet will produce two broken <img>, while in Firefox it will produce two 300x150px transparent images, and in Safari, it will produce one 300x150px and one broken image).

I'm not sure yet who is right, but in both cases, the URL is actually correctly broken, as is seen by trying to navigate your link after it's been revoked. If in Firefox we still have the image loaded it's because the <img> does use a list of available images and they don't remove it from there. So they still have the inner image data available, even after you revoke the URL. Similarly if you did fetch() that URL, you'd get a copy of the data, and that copy would still be alive even after the URL got revoked. If you did navigate to that URL (e.g through an <iframe>, the loaded document would still be alive. But the initial link between the URL and the original Blob's data is well broken. The Garbage Collector can do its job and remove the Blob from the memory.

But I should note that since here you are in the very particular case of creating a blob: URI from a File selected through an <input> tag, the actual Blob's data is not stored in memory, but on the user's disk. This means that, in this case, the blob: URI is actually just a pointer to data on disk, and that revoking it is almost useless since the Blob's data doesn't take any memory.

Kaiido
  • 123,334
  • 13
  • 219
  • 285