Прямая буферная память - PullRequest
1 голос
/ 07 января 2020

Мне нужно вернуть довольно большой файл из веб-запроса. Размер файла составляет около 670 МБ. По большей части это будет работать нормально, но через некоторое время будет выдано следующее сообщение об ошибке:

java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:694) ~[na:1.8.0_162]
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) ~[na:1.8.0_162]
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) ~[na:1.8.0_162]
    at sun.nio.ch.Util.getTemporaryDirectBuffer(Util.java:241) ~[na:1.8.0_162]
    at sun.nio.ch.IOUtil.read(IOUtil.java:195) ~[na:1.8.0_162]
    at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:159) ~[na:1.8.0_162]
    at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65) ~[na:1.8.0_162]
    at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109) ~[na:1.8.0_162]
    at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103) ~[na:1.8.0_162]
    at java.nio.file.Files.read(Files.java:3105) ~[na:1.8.0_162]
    at java.nio.file.Files.readAllBytes(Files.java:3158) ~[na:1.8.0_162]

Я установил размер кучи равным 4096 МБ, который, я думаю, должен быть достаточно большим для обработки файлов такого типа. Кроме того, когда произошла эта ошибка, я взял heapdump с jmap для анализа текущего состояния. Я нашел два довольно больших байта [], которые должны быть файлом, который я хочу вернуть. Но размер кучи составляет всего около 1,6 ГБ, а не около настроенного 4 ГБ.

Согласно другому ответу ({ ссылка }) на аналогичный вопрос, я попытался запустить инструкцию g c перед возвратом этого файла. Проблема все еще произошла, но теперь только spardi c. Проблема возникла через некоторое время, но потом, когда я устал выполнять тот же запрос снова, кажется, что сборщик мусора позаботился обо всем, что вызвало проблему, но этого недостаточно, поскольку проблема, очевидно, все еще может возникать. Есть ли другой способ избежать этой проблемы с памятью?

1 Ответ

0 голосов
/ 08 января 2020

Фактические буферы памяти, управляемые DirectByteBuffer, не выделяются в куче. Они размещаются с использованием Unsafe.allocateMemory, который выделяет «родную память». Поэтому увеличение или уменьшение размера кучи не поможет.

Когда G C обнаруживает, что на DirectByteBuffer больше нет ссылки, Cleaner используется для освобождения собственной памяти. Однако это происходит на этапе после сбора данных, поэтому, если спрос на / оборот прямых буферов слишком велик, возможно, что сборщик не сможет идти в ногу. Если это произойдет, вы получите OOME.


Что вы можете с этим поделать?

AFAIK, единственное, что вы можете сделать, это форсировать более частые сборки мусора. Но это может иметь последствия для производительности. И я не думаю, что это гарантированное решение.

Реальное решение - выбрать другой подход.

Вы видите, что вы обслуживаете множество очень больших файлов с веб-сервера, и трассировка стека показывает, что вы используете Files::readAllBytes для загрузки их в память, а затем (предположительно) отправляете их, используя один write. Предположительно, вы делаете это, чтобы получить максимально быстрое время загрузки. Это ошибка:

  • Вы связываете много памяти (многократное использование сборщика мусора и нагрузка на него. Это приводит к большему количеству запусков G C и случайным OOME. также может по-разному влиять на другие приложения на вашем сервере.

  • Узким местом для передачи файла является , вероятно , а не процесс чтения данных с диска. (Реальный Узким местом является обычно отправка данных через поток TCP по сети или запись их в файловую систему на стороне клиента.)

  • Если вы читаете Последовательно с большим файлом современная ОС Linux обычно будет использовать для чтения вперед несколько блоков дисков и удерживать блоки в буферном кеше (ОС). Это уменьшит задержку системных вызовов read, создаваемых вашим приложением.

Таким образом, для файлов такого размера лучшей идеей является потоковая передача файла. Либо выделите большой (несколько мегабайт) ByteBuffer и выполните чтение / запись в al oop, * 1039. * или * 104 0 * используйте копию файла, используя Files::copy(...) ( javado c), который должен позаботиться о буферизации для вас.

(Существует также возможность использования чего-либо, сопоставленного с системным вызовом Linux sendfile. Это копирует данные из одного файлового дескриптора в другой, не записывая их в буфер пространства пользователя.)

...