Вы определенно НЕ хотите загружать файл размером 300 МБ в один большой буфер с Java. То, что вы делаете, должно быть более эффективным для больших файлов, чем просто использование обычного ввода-вывода, но когда вы запускаете Matcher
для всего файла, отображенного в память, как вы, вы можете очень легко исчерпать память.
Во-первых, ваша кодовая память отображает файл в память ... это будет потреблять 300 Мегабайт памяти в вашем виртуальном адресном пространстве, так как файл mmap
заносится в него, хотя это за пределами кучи. (Обратите внимание, что 300 мегабайт виртуального адресного пространства связаны до тех пор, пока MappedByteBuffer
не станет сборщиком мусора . См. Обсуждение ниже. JavaDoc для map
предупреждает вас об этом. ) Затем вы создаете ByteBuffer
, поддерживаемый этим mmap
ed файлом. Это должно быть хорошо, так как это просто «просмотр» ed-файла mmap
и, следовательно, он должен занимать минимум дополнительной памяти. Это будет маленький объект в куче с «указателем» на большой объект вне кучи. Затем вы декодируете это в CharBuffer
, что означает, что вы делаете копию буфера 300 МБ, но вы делаете копию 600 МБ (в куче), потому что char
составляет 2 байта.
Чтобы ответить на комментарий и, конечно же, взглянуть на исходный код JDK, при вызове map()
в качестве OP вы фактически отображаете весь файл в память. Глядя на собственный код Windows openJDK 6 b14 sun.nio.ch.FileChannelImpl.c
, сначала он вызывает CreateFileMapping
, а затем вызывает MapViewOfFile
. Если посмотреть на этот источник, если вы попросите отобразить весь файл в памяти, этот метод будет работать именно так, как вы просите. Цитировать MSDN:
Отображение файла делает указанную часть файла видимой в
адресное пространство вызывающего процесса.
Для файлов, размер которых превышает адресное пространство, вы можете отобразить только небольшую часть
данных файла за один раз. Когда первый вид завершен, вы можете удалить его и
отобразить новый вид.
Как OP вызывает карту, «указанная часть» файла - это весь файл. Это не будет способствовать исчерпанию heap , но может способствовать исчерпанию виртуального адресного пространства, которое все еще является ошибкой OOM. Это может убить ваше приложение так же тщательно, как и исчерпание кучи.
Наконец, когда вы делаете Matcher
, Matcher
потенциально делает больше копий этих 600 МБ CharBuffer
, в зависимости от того, как вы его используете. Уч. Это много памяти, используемой небольшим количеством объектов! Учитывая Matcher
, каждый раз, когда вы звоните toMatchResult()
, вы сделаете String
копию всего CharBuffer
. Кроме того, каждый раз, когда вы звоните replaceAll()
, в лучшем случае вы сделаете String
копию всего CharBuffer
. В худшем случае вы сделаете StringBuffer
, который будет медленно расширяться до полного размера replaceAll
результата (применяя большую нагрузку на память кучи), а затем сделаете String
из этого.
Таким образом, если вы наберете replaceAll
для Matcher
для файла размером 300 МБ, и ваше совпадение будет найдено, то вы сначала сделаете серию все более крупных StringBuffer
с, пока не получите тот, который 600 МБ. Затем вы сделаете String
копию этого StringBuffer
. Это может быстро и легко привести к истощению кучи.
Вот суть: Matcher
s не оптимизированы для работы с очень большими буферами. Вы можете очень легко и без планирования сделать несколько очень больших объектов. Я обнаружил это, когда делал нечто достаточно похожее на то, что вы делаете, и сталкивался с истощением памяти, а затем просматривал исходный код Matcher
.
ПРИМЕЧАНИЕ: нет звонка unmap
. Как только вы вызываете map
, виртуальное адресное пространство вне кучи, связанной с MappedByteBuffer
, застревает там до тех пор, пока MappedByteBuffer
не соберет мусор. В результате вы не сможете выполнять определенные операции с файлом (удалять, переименовывать, ...) до тех пор, пока MappedByteBuffer
не будет собран сборщиком мусора. Если для разных файлов вызывать карту достаточно раз, но в куче недостаточно памяти, чтобы вызвать сборку мусора, вы можете выйти из памяти вне кучи. Для обсуждения см. Ошибка 4724038 .
В результате всего вышеприведенного обсуждения, если вы будете использовать его для создания Matcher
для больших файлов, и вы будете использовать replaceAll
на Matcher
, тогда ввод-вывод с отображением в память вероятно, не путь. Он просто создаст слишком много больших объектов в куче, а также израсходует много вашего виртуального адресного пространства вне кучи. В 32-битной Windows у вас есть только 2 ГБ (или, если вы изменили настройки, 3 ГБ) виртуального адресного пространства для JVM, и это приведет к значительному давлению памяти как внутри, так и снаружи кучи.
Я прошу прощения за длину этого ответа, но я хотел быть тщательным. Если вы считаете, что какая-либо часть вышеперечисленного неверна, пожалуйста, прокомментируйте и скажите так. Я не буду делать ответные отрицательные голоса. Я очень уверен, что все вышесказанное является точным, но если что-то не так, я хочу знать.