2016-11-11 3 views
8

Я только что обнаружил ошибку в моем коде библиотеки opensrc, который выделяет большой буфер для внесения изменений в большой файл flac, ошибка возникает только на старом ПК машины с 3Гб памяти с использованием Java 1.8.0_74 25,74-b02 32bitКак избежать ошибки mapFailed() при записи в большой файл в системе с ограниченной памятью

Первоначально я просто выделить буфер

ByteBuffer audioData = ByteBuffer.allocateDirect((int)(fc.size() - fc.position())); 

Но какое-то время у меня есть как

MappedByteBuffer mappedFile = fc.map(MapMode.READ_WRITE, 0, totalTargetSize); 

Мое (неправильное) понимание заключалось в том, что отображаемые буферы используют меньше памяти, чем прямой буфер, потому что весь отображаемый буфер не должен быть в памяти в то же время только используемой частью. Но этот ответ говорит, что с помощью отображенных байт буфера это плохая идея, так Im не qwuite ясно, как она работает

Java Large File Upload throws java.io.IOException: Map failed

Полный код можно увидеть на here

+0

Он потерпел неудачу, потому что он просто не может выделить столько места _address_ на 32 бит. Речь идет не о физической нехватке оперативной памяти. –

+0

, но размер файла составляет всего 200 Мб, это должно быть хорошо shoudnt it –

+0

Вы не можете быть уверены, ему нужен смежный блок такого размера. –

ответ

2

Хотя отображаемый буфер может использовать меньшую физическую память в любой момент времени, для него все еще требуется доступное (логическое) адресное пространство, равное суммарному (логическому) размеру буфера. Чтобы ухудшить ситуацию, он может (возможно) потребовать, чтобы адресное пространство было смежным. По какой-то причине этот старый компьютер не может обеспечить достаточное дополнительное логическое адресное пространство. Двумя вероятными объяснениями являются (1) ограниченное логическое адресное пространство + весовые требования к буферной памяти и (2) некоторое внутреннее ограничение, которое ОС накладывает на объем памяти, который может быть отображен как файл для ввода-вывода.

Что касается первой возможности, рассмотрим тот факт, что в системе виртуальной памяти каждый процесс выполняется в своем собственном логическом адресном пространстве (и, следовательно, имеет доступ к полной адресации по адресу 2^32 байта). Поэтому, если - в момент времени, когда вы пытаетесь создать экземпляр MappedByteBuffer - текущий размер процесса JVM плюс общий (логический) размер MappedByteBuffer превышает 2^32 байта (~ 4 гигабайта), тогда вы столкнулись бы с OutOfMemoryError (или любой другой ошибкой/исключением, выбранным классом, например, IOException: Map failed).

Что касается второй возможности, возможно, самый простой способ оценить это - профилировать вашу программу/JVM при попытке создать экземпляр MappedByteBuffer. Если выделенная память JVM-процесса + требуемая totalTargetSize значительно ниже потолка 2^32 байта, но вы по-прежнему получаете ошибку с ошибкой «карта», то вполне вероятно, что некоторые внутренние ограничения ОС на размер файлов с отображением памяти первопричиной.

Итак, что это значит, насколько это возможно, решения?

  1. Просто не используйте этот старый компьютер.(желательно, но, вероятно, не представляется возможным)
  2. Удостоверьтесь, что все остальное в вашей JVM имеет как можно меньшую площадь памяти для срока службы MappedByteBuffer. (правдоподобно, но, возможно, нерелевантно и определенно непрактично)
  3. Разбить этот файл на более мелкие куски, а затем работать только на одном куске за раз. (может зависеть от характера файла)
  4. Используйте другой буфер меньшего размера. ... и просто терпит снижение производительности. (это наиболее реалистичным решением, даже если это самый расстраивает)

Кроме того, что именно является totalTargetSize для проблемной случае?


EDIT:

После выполнения некоторого копания, представляется очевидным, что IOException обусловлено running out of address space in a 32-bit environment. Это может произойти даже тогда, когда сам файл находится под 2^32 байтами из-за отсутствия достаточного смежного адресного пространства или из-за других достаточно больших требований адресного пространства в JVM в то же время в сочетании с большими MappedByteBuffer запрос (see comments). Чтобы быть ясным, IOE все еще может быть выброшен, а не OOM even if the original cause is ENOMEM. Более того, по-видимому, существуют проблемы со старыми [вставить здесь Microsoft OS] 32-разрядными средами, в частности (example, example).

Так что, похоже, у вас есть три основных варианта.

  1. Использовать «the 64-bit JRE or...another operating system» в целом.
  2. Используйте меньший буфер другого типа и работайте с файлом в кусках. (и принять удар производительности из-за того, что не используется сопоставленный буфер)
  3. Продолжите использовать MappedFileBuffer по соображениям производительности, но также можете работать с файлом в меньших кусках, чтобы обойти ограничения адресного пространства.

Причины я положить используя MappedFileBuffer в более мелких кусках, как третий из-за устоявшиеся и нерешенные проблемы в unmapping MappedFileBuffer (example), которая является то, что вы обязательно должны сделать между обработкой каждого фрагмента в чтобы избежать попадания 32-битного потолка из-за суммарного адресного пространства накопленных сопоставлений. (ПРИМЕЧАНИЕ: это применимо только в том случае, если это 32-битный потолок адресного пространства, а не некоторые внутренние ограничения ОС, которые являются проблемой ... если последнее, а затем игнорировать этот абзац) Вы можете попробовать this strategy (удалите все ссылки, затем запустите GC), но вы, по сути, будете во власти того, как GC и ваша базовая ОС взаимодействуют в отношении файлов с отображением памяти. И другие потенциальные обходные пути, которые пытаются манипулировать базовым файлом с отображением памяти более или менее напрямую (example), чрезвычайно опасны и специально осуждены Oracle (see last paragraph). Наконец, учитывая, что поведение GC по-прежнему ненадежно, и, кроме того, официальная документация явно заявляет, что «many of the details of memory-mapped files [are] unspecified», я бы не рекомендую использовать MappedFileBuffer, как это независимо от любого обходного пути, о котором вы можете прочитать.

Так что, если вы не хотите рисковать, я предлагаю либо следовать явным советам Oracle (пункт 1), либо обрабатывать файл как последовательность меньших фрагментов с использованием другого типа буфера (точка 2).

+0

Спасибо, что решение, которое я сделал, 4>, размер отображаемого буфера, который я ранее создавал, составлял около 200 мб. –

+0

@PaulTaylor. Дайте мне знать, как это происходит. См. Мои изменения (второй раздел) для моей подробной информации + ссылки на проблему. Кроме того, см. Третий абзац (отредактированный) в первом разделе, если вы достаточно любопытны, чтобы попытаться диагностировать проблему, а не просто обойти ее. – Travis

+0

Хорошо, спасибо, я не могу использовать только 64-битный, потому что мне нужно продолжать поддерживать 32-битную, я продолжаю использовать bytebuffer в кусках и забываю об использовании MappedByteBuffer. –

1

При выделении буфера, вы в основном получаете кусок виртуальной памяти с вашей операционной системы (и эта виртуальная память конечна, а верхняя теоретическая - ваша оперативная память + независимо от того, какая своп сконфигурирована - все остальное было захвачено первыми другими программами и ОС)

Карта памяти просто добавляет пространство, занятое на вашем на диске файл для вашего virtua l память (хорошо, есть некоторые накладные расходы, но не так много), чтобы вы могли получить больше.

Ни один из них не должен присутствовать в ОЗУ постоянно, его части могут быть заменены на диск в любой момент времени.