Освобождение памяти от создания блоба / URL объекта при записи файла на диск клиента

Обновить

Так как, задавая вопрос ниже и получив более фундаментальный вопрос после обнаружения ошибки в коде, я нашел больше информации, например, в веб-документах MDN для метода downloads API downloads.download(), в котором говорится, что отзыв объекта URL должен выполняться только после того, как файл / URL был загружен. Итак, я потратил некоторое время, пытаясь понять, делает ли веб-расширение событие загрузки onChanged доступным для javascript веб-страницы, и не думаю, что это так. Я не понимаю, почему API-интерфейс загрузок доступен только для расширений, особенно когда возникает довольно много вопросов, касающихся этой же проблемы с использованием памяти / объектом-url-отзывом. Например, дождитесь, пока пользователь закончит загрузку BLOB-объекта в Javascript.

Если вы знаете, не могли бы вы объяснить? Спасибо.


Начиная с закрытого браузера Firefox и щелкая правой кнопкой мыши локальный html-файл, чтобы открыть его в Firefox, он открывается пятью процессами firefox.exe, как показано в диспетчере задач Windows. Четыре из этих процессов начинаются с 20 000–25000 КБ памяти, а один - с 115000 КБ.

Эта HTML-страница имеет базу данных indexedDB с 50 хранилищами объектов, каждое из которых содержит 50 объектов. Каждый объект извлекается из его хранилища объектов и преобразуется в строку с использованием JSON.stringify и записывается в двумерный массив. После этого все элементы массива объединяются в одну большую строку, преобразуются в большой двоичный объект и записываются на жесткий диск через объект URL, который сразу же отзывается. Конечный файл составляет около 190 МБ.

Если код останавливается непосредственно перед преобразованием в BLOB-объект, использование памяти одним из процессов firefox.exe увеличивается примерно до 425000 КБ, а затем уменьшается до 25000 К примерно через 5-10 секунд после того, как элементы массива были объединены в одна строка

Если код запускается до конца, использование памяти тем же процессом firefox.exe возрастает примерно до 1 000 000 000, а затем падает до примерно 225000 000. Процесс firefox.exe, который начался с 115000 КБ, также увеличивается на этапе blob-кода до примерно 325000 КБ и никогда не уменьшается.

После записи большого двоичного объекта на диск в виде текстового файла эти два процесса firefox.exe никогда не освобождают приблизительно 2 x 200 000 КБ памяти.

Я установил для каждой переменной, используемой в каждой функции, значение null, и память никогда не освобождается, если страница не обновляется. Кроме того, этот процесс инициируется событием нажатия кнопки; и если он запускается снова без промежуточного обновления, каждый из этих двух процессов firefox.exe захватывает дополнительно 200 000 КБ памяти при каждом запуске.

Я не смог понять, как освободить память?

Две функции довольно просты. json[i][j] содержит строковую версию j-го объекта из i-го хранилища объектов в базе данных. os_data[] - это массив небольших объектов { "name": objectStoreName, "count": n }, где n - количество объектов в хранилище. Функция build_text освобождает память, если write_to_disk не вызывается. Таким образом, проблема, кажется, связана с BLOB-объектом или URL-адресом.

Я, наверное, упускаю из виду что-то очевидное. Спасибо за любое направление, которое вы можете предоставить.

РЕДАКТИРОВАТЬ:

Я вижу из JavaScript: создайте и сохраните файл, в котором есть ошибка в отчете 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

1 ответ

Решение

Я немного растерялся, что за вопрос здесь...

Но давайте попробуем ответить хотя бы на часть этого:

Для начала давайте объясним, что URL.createObjectURL(blob) примерно делает:

Это создает URI BLOB-объекта, который является URI, указывающим на BLOB-объект blob в памяти, как если бы он был в доступном месте (например, на сервере).
Этот BLRI URI помечает blob как не подлежащий сборке сборщиком мусора (GC) до тех пор, пока он не был отозван, так что вам не нужно поддерживать прямую ссылку на blob в вашем скрипте, но вы все равно можете использовать / загрузить его.

URL.revokeObjectURL затем прервет связь между URI BLOB и BLOB в памяти. Это не освободит память, занятую blob напрямую, он просто снимет свою собственную защиту относительно GC, [и больше никуда не укажет].
Таким образом, если у вас есть несколько URI большого двоичного объекта, указывающих на один и тот же объект Blob, отзыв только одного не нарушит другие URI большого двоичного объекта.

Теперь память будет освобождена только тогда, когда включится GC, и это решают только внутренние компоненты браузера, когда он считает, что это лучшее время, или когда он видит, что у него нет других вариантов (как правило, когда он пропускает пространство памяти)).

Так что вполне нормально, что вы не видите, что ваша память освобождается мгновенно, и по опыту я бы сказал, что FF не заботится об использовании большого количества памяти, когда она доступна, что заставляет GC пинать не так часто, что хорошо для пользовательского опыта (GCing часто приводит к задержкам).


На ваш вопрос о загрузке, действительно, веб-API не предоставляют способ узнать, была ли загрузка успешной или неудачной, и даже если она только что закончилась.
Для отзывающей части это действительно зависит от того, когда вы это делаете.
Если вы сделаете это непосредственно в обработчике кликов, то браузер еще не выполнит запрос предварительной выборки, поэтому, когда произойдет действие щелчка по умолчанию (загрузка), ничего не будет связано с URI больше.
Теперь, если вы действительно отмените URI BLOB-объекта после запроса "сохранить", браузер выполнит запрос предварительной выборки и, таким образом, сможет сам пометить, что ресурс 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});
};
<a id="anchor" download="foo.txt">download</a>

Другие вопросы по тегам