Как избежать OutOfMemoryError при использовании Bytebuffers и NIO?

Я использую ByteBuffers и FileChannels для записи двоичных данных в файл. Делая это для больших файлов или последовательно для нескольких файлов, я получаю исключение OutOfMemoryError. Я где-то читал, что использование Bytebuffers с NIO не работает, и его следует избегать. Кто-нибудь из вас уже сталкивался с такой проблемой и нашел решение для эффективного сохранения больших объемов двоичных данных в файл в java?

Подходит ли вариант jvm -XX:MaxDirectMemorySize?


person jumar    schedule 26.08.2008    source источник


Ответы (6)


Я бы сказал, не создавайте огромный ByteBuffer, который содержит ВСЕ данные сразу. Создайте гораздо меньший ByteBuffer, заполните его данными, а затем запишите эти данные в FileChannel. Затем сбросьте ByteBuffer и продолжайте, пока все данные не будут записаны.

person Outlaw Programmer    schedule 26.08.2008

Ознакомьтесь с отображенными байтовыми буферами Java, также известные как «прямые буферы». По сути, этот механизм использует систему подкачки виртуальной памяти ОС для «отображения» вашего буфера непосредственно на диск. ОС будет управлять перемещением байтов на/с диска и в память автоматически, очень быстро, и вам не придется беспокоиться об изменении параметров виртуальной машины. Это также позволит вам воспользоваться улучшенной производительностью NIO по сравнению с традиционным вводом-выводом на основе потока Java без каких-либо странных хаков.

Единственные два улова, о которых я могу думать, это:

  1. В 32-разрядной системе вы ограничены чуть менее 4 ГБ всего для всех сопоставленных байтовых буферов. (На самом деле это ограничение для моего приложения, и теперь я работаю на 64-битных архитектурах.)
  2. Реализация специфична для JVM и не является обязательным требованием. Я использую Sun JVM и проблем нет, но YMMV.

Кирк Пеппердин (несколько известный гуру производительности Java) связан с веб-сайтом www.JavaPerformanceTuning.com, на котором есть дополнительные сведения о MBB: Советы по повышению производительности NIO

person Stu Thompson    schedule 26.08.2008
comment
Спасибо за указание на то, что существует ограничение для всех отображаемых буферов байтов (только моего приложения или всех в ОС?). В моем случае я получаю глупое исключение OutOfMemoryException, даже когда я пытаюсь использовать один MappedByteBuffer для файла размером около 1,6 ГБ! Но почему? Как я должен узнать, насколько велико мое оставшееся пространство?? Помощь! - person Zordid; 21.09.2012
comment
@Zordid Эммм... MappedByteBuffers (Mapped is key!) обычно не вызывают OutOfMemoryExceptions. Что-то еще сломано. Я бы предложил создать новый вопрос здесь, в StackOverflow... с кодом! Возможно, кто-то сможет вам помочь. - person Stu Thompson; 21.09.2012

Если вы обращаетесь к файлам случайным образом (читать здесь, пропускать, писать там, возвращаться назад), то у вас проблема ;-)

Но если вы пишете только большие файлы, вам следует серьезно рассмотреть возможность использования потоков. java.io.FileOutputStream можно использовать непосредственно для записи файла байт за байтом или завернуть в любой другой поток (например, DataOutputStream, ObjectOutputStream) для удобства записи чисел с плавающей запятой, целых чисел, строк или даже сериализуемых объектов. Подобные классы существуют для чтения файлов.

Потоки обеспечивают удобство работы с сколь угодно большими файлами в (почти) сколь угодно маленьком объеме памяти. В подавляющем большинстве случаев они являются предпочтительным способом доступа к файловой системе.

person Marcin    schedule 26.08.2008

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

person Cagatay    schedule 26.08.2008

Это может зависеть от конкретного поставщика и версии JDK.

В некоторых Sun JVM есть ошибка в GC. Нехватка прямой памяти не приведет к запуску GC в основной куче, но прямая память заблокирована прямым мусором ByteBuffers в основной куче. Если основная куча в основном пуста, многие из них не собираются в течение длительного времени.

Это может сжечь вас, даже если вы не используете прямые буферы самостоятельно, потому что JVM может создавать прямые буферы от вашего имени. Например, запись непрямого ByteBuffer в SocketChannel создает под прикрытием прямой буфер, который будет использоваться для фактической операции ввода-вывода.

Обходной путь — самостоятельно использовать небольшое количество прямых буферов и хранить их для повторного использования.

person Darron    schedule 26.09.2008

Предыдущие два ответа кажутся вполне разумными. Что касается того, будет ли работать переключатель командной строки, это зависит от того, насколько быстро использование вашей памяти достигнет предела. Если у вас недостаточно оперативной и виртуальной памяти, чтобы как минимум утроить доступную память, вам нужно будет использовать одно из приведенных альтернативных предложений.

person Dana the Sane    schedule 26.08.2008