Sunday, 8 November 2009

boolean - боремся за Java память...

У Вас когда-либо случались такие ситуации, когда Ваше Java приложение трещит по швам? В моём случае это случилось из-за нехватки доступной оперативной памяти. И, естественно, обнаружилась нехватка в самый неподходящий момент: на носу очередной долгожданный релиз, один из серверов остановлен для обновления кода и данных и реинкарнация старого кода уже невозможна, в ближайшие дни запланировано несколько совещаний и собеседований, что сильно отвлекает от процесса оптимизации - в общем, ЧП не прошло незамеченным. 

К слову сказать, сделай я правильный backup и экстренные работы по восстановлению жизнеспособности прошли бы гораздо более спокойно, но это была бы уже совсем другая история. Итак в моём распоряжении есть код, которому не хватает 15Gb оперативной памяти для нормального функционирования и очень длительный и дорогостоящий процесс запуска (около 5 часов), в ходе работы которого можно только сидеть со скрещенными пальцами и надеятся, что в этот раз заветные слова OutOfMemoryError не появятся в консоли удалённого сервера. 

Не буду описывать всех ухищрений, которые пришлось проделать, чтобы восстановить остановленный сервер в течении трёх дней, но одним своим мини открытием поделюсь - boolean - это не тот тип данных, который Вы хотите использовать в высоконагруженных системах. Внимание вопрос: 

Как Вы думаете, сколько памяти занимает boolean например на Ubuntu server x64? 

Правильным ответом будет: неизвестно и зависит только от реализации JVM. 

Рассмотрим распространённую Sun JVM и прочтем в спецификации виртуальной машины, что boolean типа в ней нет как такового, вместо него используется int! А это означает, что для хранения значения типа "да\нет" используется ровно 32 бита, независимо от архитектуры процессора. Правда в том же разделе мы видим, что произведена оптимизация для работы с массивами boolean, которые преобразуются в массив байт, что даёт прирост доступной памяти в 4 раза. И всё же платить за хранение нолика или еденички семью лишними битами - иногда просто кощунство и издевательство над серверами (особенно при размерах массивов в 500 миллионов элементов). 

Спасением в таких случаях будет класс BitSet, который ведёт себя подобно массиву boolean, но упаковывает данные так, что для одного бита выделяется всего один бит памяти (с небольшими издержками для всего массива). BitSet хранит внутри себя массив типа long, а при запросе или установке значения определенного бита - высчитывает индекс нужного long и пользуясь побитовыми операциями и операциями сдвига производит вычисления над единственным битом. 

Существует еще более интересная реализация BitSet, OpenBitSet - Apache реализация, которая используется для Lucene. Она гораздо быстрее, но упускает некоторые проверки, проводимые в оригинальном BitSet. Что использовать - решать Вам.

item

17 comments:

  1. ИМХО такие статьи лучше писал бы на спец ресурсах (хоть на хабре). И больше бы людей посмотрело, и интересные отзывы можно получить.

    ReplyDelete
  2. Во-первых данный блог подразумевался как технический или около технический, во-вторых долго упрашивать меня не нужно - запостил статью в песочницу Хабра.

    А вообще и здесь есть люди, которым будет это интересно.

    ReplyDelete
  3. http://java.sun.com/j2se/1.4.2/docs/api/java/lang/OutOfMemoryError.html

    OutOfMemoryError потому и Error, а не Exception, что является unrecoverable, т.е. это JVM crash. Кстати, очень популярный вопрос на собеседованиях.

    P.S. Читал на Хабре, там аккаунта не имею - поэтому комментирую тут.

    ReplyDelete
  4. Да, виноват, согласен.
    Это не мешает ему вызывать исключительную ситуацию, но поправить стоит.

    ReplyDelete
  5. Видел статью на Хабре.

    Занимаюсь похожими задачами - парсеры больших текстовых файлов (~1 GB) на java. Предидущие парсеры работали по 5-10 часов. Оптимизированные варианты работают считанные минуты - за счет оптимизации работы с диском, построения индексов, распараллеливание чтения, оптимизации структур хранения.

    1. Для экономии памяти не пробовали ли использовать CompressedOops?
    http://wikis.sun.com/display/HotSpotInternals/CompressedOops

    2. Так же можно упаковывать данные быстрыми пакерами типа http://www.quicklz.com

    Алексей.

    ReplyDelete
  6. Мои файлы в разы больше. В запакованном .bz2 состоянии порядка 10Gb и растут.

    За ссылки спасибо - поштудируем.

    ReplyDelete
  7. Бодрые объемы - интересные задачи! Ну тогда похоже на затык IO, напрашивается распараллеливание процессинга :)

    ReplyDelete
  8. Еще можно GC потюнить, на таких объемах возможны неприятные паузы:
    http://gregluck.com/blog/archives/2006/07/how-we-solved-our-garbage-collection-pausing-problem/

    ReplyDelete
  9. Да Вы просто кладязь полезных ссылок. Спасибо большое :)

    ReplyDelete
  10. Для ускорения (обещают 2-5 раз :) XML парсера можно попробовать следущее:

    http://javolution.org/target/site/apidocs/javolution/xml/sax/SAX2ReaderImpl.html

    ссылка на бенчмарки:
    http://www.xml.com/lpt/a/1702

    ReplyDelete
  11. Алексей, Вам аккаунт на Хабре нужен? Как только появится возможность - сразу скину Вам :)

    ReplyDelete
  12. Спасибо, от аккаунта не откажусь :)

    ReplyDelete
  13. Да, еще, просто как идея распараллеливания обработки - http://www.hazelcast.com/ для Distributed java.util.{Queue, Set, List, Map} :)

    ReplyDelete
  14. По поводу космических объемов данных в 10 GB в архиве. Я как понимаю речь идет о planet-latest.osm.bz2 (openstreenmap xml формате)? Насколько я заметил, то там есть инкрементные дневные апдейты вменяемых размеров (десятки MB) или напиленные на континеты и страны фрагменты:
    http://downloads.cloudmade.com/, если все-таки уперлость заливать целым куском :)

    ReplyDelete
  15. Спасибо, но про OSM я знаю больше, чем Вам кажеться :-)
    Напилением на страны, с некоторых пор занимаюсь тоже я ;)

    А в данном, конкретном случае мне нужен весь файл планеты целиком. Иначе, к сожалению, пока не получается.

    ReplyDelete
  16. А, Семен Семенович (с), теперь все встало на свои места!

    http://planet.openstreetmap.org/ +
    http://algo2.iti.kit.edu/routeplanning.php =
    http://developers.cloudmade.com/projects/show/routing-http-api :)

    ReplyDelete
  17. Вот и познакомились :-)
    Обещание инвайта в силе - как только появиться: отпишусь.

    ReplyDelete