Я разрабатываю приложение на Java, которое работает на устройствах Windows Mobile. Чтобы достичь этого, мы использовали JMM Esmertec JBed, который не идеален, но мы застряли с ним на данный момент. Недавно мы получали жалобы от клиентов об OutOfMemoryErrors. После многих игр с вещами я обнаружил, что устройство имеет достаточно свободной памяти (около 4 МБ).
OutOfMemoryErrors всегда встречаются в одной и той же точке кода, а именно при расширении StringBuffer, чтобы добавить к нему некоторые символы. После добавления некоторых журналов вокруг этой области я обнаружил, что у моего StringBuffer было около 290000 символов с емкостью около 290500. Стратегия расширения внутреннего массива символов просто удваивает размер, поэтому он будет пытаться выделить массив около 580000 символов. Я также распечатал использование памяти в это время и обнаружил, что он использует около 3,8 МБ около 6,8 МБ (хотя я видел, что общая доступная память увеличивается примерно до 12 МБ, поэтому есть много возможностей для расширения). Так вот, в этот момент приложение сообщает об OutOfMemoryError, что не имеет особого смысла, сколько еще доступно.
Я начал думать о работе приложения до этого момента. В основном, что происходит, я разбираю XML файл, используя MinML (небольшой XML Sax Parser). В одном из полей XML содержится около 300 тыс. Символов. Парсер передает данные с диска, и по умолчанию он загружает только 256 символов за раз. Поэтому, когда он достигает поля, о котором идет речь, парсер будет вызывать метод "characters()" обработчика более 1000 раз. Каждый раз он создает новый char [], содержащий 256 символов. Обработчик просто добавляет эти символы в StringBuffer. Первоначальный размер StringBuffer по умолчанию равен 12, так что символы добавляются в буфер, и он должен расти несколько раз (каждый раз создавая новый char []).
Мое предположение заключалось в том, что возможно, что, хотя имеется достаточно свободной памяти, так как предыдущий char [] s может быть собран в мусор, возможно, нет смежного блока памяти, достаточно большого, чтобы соответствовать новому массиву, который я пытаюсь выделить. И, возможно, JVM недостаточно умен, чтобы увеличить размер кучи, потому что он глуп и считает, что нет необходимости, потому что, по-видимому, достаточно свободной памяти.
Итак, мой вопрос: есть ли у кого-нибудь опыт этой JVM и он может окончательно подтвердить или опровергнуть мои предположения о распределении памяти? А также, есть ли у кого-нибудь какие-либо идеи (при условии, что мои предположения верны) о том, как внедрить распределение массивов, чтобы память не стала фрагментированной?
Примечание: все, что я уже пробовал:
- Я увеличил размер начального массива StringBuffer, и я увеличил размер чтения анализатора, чтобы ему не нужно было создавать так много массивов.
- Я изменил стратегию расширения StringBuffer так, что как только он достиг порога определенного размера, он будет расширяться только на 25%, а не на 100%.
Выполнение обоих этих действий немного помогло, но по мере увеличения размера данных xml, которые я получаю, я все еще получаю OutOfMemoryErrors при довольно низком размере (около 350kb).
Еще одна вещь, которую нужно добавить: все это тестирование было выполнено на устройстве с использованием JVM. Если я запускаю тот же код на рабочем столе с помощью Java SE 1.2 JVM, у меня нет никаких проблем или, по крайней мере, я не получаю проблему до тех пор, пока мои данные не достигнут размером около 4 МБ.
EDIT:
еще одна вещь, которую я только что попробовал, которая немного помогла - я установил Xms в 10M. Таким образом, это устраняет проблему JVM, которая не расширяет кучу, когда это необходимо, и позволяет обрабатывать больше данных до возникновения ошибки.