Вход |  Регистрация

Все Тэги

Monitoring Java: Стандартные «горячие точки»

05.08.20131325 просм.

В нашей предыдущей заметке мы рассказывали о том как J2EE управляет приложениями, начали описывать стандартные «горячие точки», за которыми стоит понаблюдать, и предложили решения для максимизации доступности приложений, с помощью осведомленности о потенциальных проблемах. В этой заметке мы расскажем о том, как Java Virtual Machine управляет памятью.

Сборка мусора

То, как приложение использует память, может существенно отразиться на его производительности. Нечто простое, вроде создания нового экземпляра событийного объекта и его передача между уровнями Web и EJB, может показаться безобидным во время разработки и модульного тестирования, но при нагрузочном тестировании оказать куда более значительное влияние на память. Рассмотрим пример. Предположим, каждый запрос использует 10 Кб памяти. Умножим это на 500 пользователей, которые одновременно используют приложение и делают запросы в среднем раз в 5 секунд каждый. За 5 минут работы объем памяти, выделенной для этого объекта в 10 Kб, составит 300 Мб. Возьмём в расчет и другие объекты, которые в этот момент создаются – и вот уже сборка мусора становится приоритетным вопросом.

Одним из преимуществ Java является то, что виртуальная машина управляет всей памятью за нас. С одной стороны, это, конечно, удобно. С другой стороны, в то время как мы не обременены задачей управления памятью, мы действительно не можем ею управлять. Java Virtual Machine (JVM) содержит поток, который наблюдает за состоянием памяти и освобождает её по мере необходимости. Существуют различные виртуальные машины, но в нашей заметке мы сосредоточимся на Sun Java Virtual Machine версии 1.3.1 (поскольку она до сих пор поставляется с большинством продуктивных серверов приложений).

Sun JVM управляет памятью, поддерживая два отдельных пространства поколений, в которых могут размещаться объекты: «молодое» поколение и «старшее» поколение. Управляя «молодым» поколением, JVM может воспользоваться тем, что большинство объектов создаются очень ненадолго и доступны для сборки мусора в самое ближайшее время после их создания. «Молодое» поколение работает очень быстро и эффективно, поскольку или освобождает неиспользуемую память, или перемещает старые объекты в «старшее» поколение, используя механизм копирования. Есть три типа сборки мусора, которые поддерживает JVM:

  • Копирование (или «уборка мусора»): эффективно перемещает объекты между поколениями; тип по умолчанию для небольших сборок;
  • Пометить-и-сжать (Mark-compact): проводит сборку памяти на месте, но значительно медленнее, чем копирование; тип по умолчанию для крупных сборок;
  • Инкрементальная (или «паровоз»): проводит постоянную сборку памяти, чтобы минимизировать количество времени, затрачиваемого на одну сборку; необходимо включать вручную с помощью аргумента “- Xincgc” командной строки.

По умолчанию сборщик мусора старается, насколько это возможно, восстановить память путем копирования объектов между поколениями (незначительные сборки), а когда использование памяти приближается к максимуму от заданного объёма, то он выполняет операцию пометить-и-сжать (крупные сборки). В среде одного приложения крупная сборка замедляет работу приложения, но при этом запускается очень редко; в масштабах корпоративных приложений, как мы уже видели на примере, операции по одному простому запросу повлекли за собой использование 300 Мб памяти за 5 минут. Крупные сборки мусора влекут за собой катастрофические последствия для производительности сервера приложений. Некоторые крупные сборки могут занимать до нескольких минут, и на протяжении этого времени сервер не отвечает на запросы и может даже отклонять входящие запросы на соединение.

Итак, как можно избежать крупных сборок? Или, если они неизбежны, как можно минимизировать их влияние на систему?

Настройка JVM включает в себя несколько шагов:

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

Стандартное поведение JVM отлично подходит для автономных приложений, но плачевно для корпоративных. Конфигурация размеров куч и поколений по умолчанию варьируется в зависимости от операционной системы, но в любом случае не оптимизирована под корпоративные приложения. Рассмотрим JVM под управлением Solaris: максимальный размер кучи 64 Мб, из которых 32 Мб распределено под «молодое» поколение. Попробуем разместить 300 Мб за 5 минут, или 60 Мб в минуту, при общем размере кучи 64 Мб – помня при этом, что эти запросы не единственное, что работает на виртуальной машине. Суть в том, что эта куча слишком мала. Следует взять за правило выделять виртуальной машине весь объем памяти, который только можно позволить себе выделить.

При расчете размера поколений следует более внимательно взглянуть на структуру «молодого» поколения: в нём есть область под названием Eden (Эдем, он же рай), где создаются объекты, и определены две области Survivor (выживший). Одна из этих областей всегда пустует и является местом для последующего копирования – объекты копируются туда и обратно между областями Survivor, пока не станут достаточно давними, чтобы их можно было отнести к «старшему» поколению. Очень важно правильно задать размеры областей Survivor, потому что если они слишком малы, то при небольшой сборке копирование всех необходимых объектов из области Eden в Survivor невозможно. А это приводит к значительному учащению сборок и преждевременному «старению» объектов. В Solaris размер областей Survivor по умолчанию составляет 1/27 всего размера «молодого» поколения, чего, по всей видимости, слишком мало для большинства корпоративных приложений.

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

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

Метки: , , ,

Добавить комментарий

Для отправки комментария вам необходимо авторизоваться.

Партнеры DevOpsHub и DevOpsWiki