Как Бороться С Паузами Gc

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

Я буду говорить о CMS (низкая пауза), так как на данный момент это наиболее часто используемый алгоритм для приложений с большим объемом памяти и низкими требованиями к задержке.

Описание дано в предположении, что ваше приложение работает на машине с большим объемом памяти и большим количеством процессоров.



Как бороться с паузами GC

Подробно описаны общие принципы работы ГХ и CMS в частности.

здесь .

Я лишь кратко резюмирую здесь то, что нам нужно для понимания этой темы:

  • Память разделена на две области YoungGen и OldGen.
  • Вновь созданные объекты попадают в YoungGen.
  • OldGen включает объекты, пережившие несколько незначительных сборок мусора.

  • Незначительная уборка мусора очищает YoungGen
  • Масштабная сборка мусора очищает OldGen
  • Полная очистка обеих областей ГХ
  • Остановить мир означает, что ваше приложение полностью останавливается во время сборки мусора.

  • Параллельные алгоритмы и фазы не приводят к остановке приложения, т. е.

    сбор мусора выполняется параллельно приложению.

  • Параллельные алгоритмы и фазы — это действия, выполняемые в нескольких потоках.

    Они могут быть как одновременными, так и остановившими мир.

    Если явно не указано иное, документация обычно означает остановку мира.

  • Незначительные сборки мусора (минорный GC) – всегда только остановка мира
  • Полный сборщик мусора останавливает мир
  • CMS (одновременная проверка маркировки) состоит из следующих основных этапов:
    • начальная отметка - остановить мир
    • отметка - Параллельно
    • предварительная очистка – параллельная
    • замечание - Остановить мир
    • развертка - одновременно
  • Поиск мертвых объектов (тех, на которые не осталось ссылок в приложении) в традиционных сборщиках мусора осуществляется путем поиска всех живых объектов (достижимых по ссылкам из корней GC)
  • CMS не выполняет дефрагментацию памяти и использует для управления ею свободные списки.

  • GC Ergonomic (параметры, задающие желаемые максимальные паузы) не работает с CMS
Итак, в нашем случае есть следующие моменты, когда наше приложение полностью останавливается.

  1. Небольшой сбор мусора
  2. Фаза инициализации CMS
  3. Фаза замечаний CMS
  4. Полный GC
Сначала вам нужно определить, настолько ли велики паузы в этих четырех случаях, что портят жизнь вашему приложению.

Лучше всего это смотреть на производственной системе, так как там есть именно те условия, которые вам нужны.

Слава Богу, это можно сделать абсолютно без снижения производительности, запустив JVM со следующими параметрами.



-verbose:gc -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime

Подробно, как читать логи CMS, можно прочитать здесь.

здесь .

Если вы хотите, чтобы время печаталось не в относительных секундах от начала JVM, а в человеческих терминах, вы можете использовать парсер, который Я написал это на Python .

Сейчас нас интересуют только части журналов о событиях остановки мира.



1. Замедляется мелкий сбор мусора.



[GC [DefNew: 209100K->25808K(235968K), 0.0828063 secs] 209100K->202964K(1284544K), 0.0828351 secs] [Times: user=0.02 sys=0.08, real=0.08 secs] Total time for which application threads were stopped: 0.0829205 seconds

Основной алгоритм минорных сборок — копирование, поэтому чем больше живых объектов в YougGen, тем больше времени занимает минорная сборка.

Я вижу как минимум три вещи, на которые стоит обратить внимание, когда паузы слишком длинные и вы не удовлетворены.

а.

Ваша JVM использует неправильный алгоритм.

В приведенном выше журнале используется однопоточный алгоритм (DefNew).

Рекомендую попробовать новый многопоточный алгоритм (в логах он будет называться ParNew), который можно принудительно включить с помощью параметра -XX:+UseParNewGC. Также можно упомянуть, что если вы видите в логах имя PSYoungGen, это означает, что ваша JVM использует параллельный алгоритм, но старой реализации.

Хотя, похоже, он недоступен в сочетании с CMS. б.

Вы выделили для YoungGen слишком большой участок памяти (в приведенном выше логе это 235968К).

Его можно уменьшить, установив параметр -Xmn. в.

Вы используете слишком много оставшегося пространства с большим допустимым возрастом объектов, поэтому объекты копируются обратно и не могут быть повышены до OldGen, создавая ненужную нагрузку на второстепенный сборщик мусора.

Эту ситуацию можно исправить с помощью параметров -XX:SurvivorRatio и -XX:MaxTenuringThreshold. Для более детального анализа этого случая вы можете запустить JVM с параметром -XX:+PrintTenuringDistribution, чтобы получить больше информации о генерации объектов в журналах GC.

2. init-mark работает долго



[GC [1 CMS-initial-mark: 680168K(1048576K)] 706792K(1284544K), 0.0001161 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Total time for which application threads were stopped: 0.0002740 seconds

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

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

Здесь можно попробовать увеличить количество потоков (ParallelCMSThreads), участвующих в этой фазе.

По умолчанию оно рассчитывается как (ParallelGCThreads + 3)/4).

Те.

если ParallelGCThreads=8, то в фазе init-mark будут участвовать только два потока, что может не дать особого выигрыша из-за накладных расходов, возникающих из-за параллелизма.



3. Длинные паузы в фазе замечания.



[GC[YG occupancy: 26624 K (235968 K)][Rescan (non-parallel) [grey object rescan, 0.0056478 secs][root rescan, 0.0001873 secs], 0.0059038 secs][weak refs processing, 0.0000090 secs] [1 CMS-remark: 750825K(1048576K)] 777449K(1284544K), 0.0059808 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Total time for which application threads were stopped: 0.0061668 seconds

На этапе маркировки запускается специальный процесс, который отслеживает все изменения ссылок.

Фаза «Примечание» нужна просто для просмотра всех измененных ссылок.

а.

Если вы видите в журналах фразу «Повторное сканирование (непараллельное)», то я рекомендую включить опцию -XX:+CMSParallelRemarkEnabled, чтобы включить несколько потоков на этом этапе.

б.

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

(Например, java.util.WeakHashMap) в.

Возможно, ваши ссылки сильно изменятся.

Посмотрите, сколько времени проходит между инициальной отметкой и ремаркой.

Чем меньше времени прошло между этими фазами, тем меньше ссылок будет изменено и тем быстрее будет работать функция примечаний.

Начиная с пятой java, непосредственно перед фазой примечаний, была добавлена еще одна фаза abortable-preclean, которая по сути ничего не делает; он просто висит и ждет, пока сработает минорная сборка, потом ждет еще немного и завершается, запуская таким образом следующую фазу реплики.

Есть две причины такой логики.

Во-первых, эта ремарка также сканирует YoungGen и для возможности работы в многопоточном режиме необходима минорная сборка, после которой становится возможным эффективно разбивать оставшиеся в YoungGen объекты на области для параллельной обработки.

А во-вторых, обратите внимание, что фаза остановки работы довольно длинная, и если она сработает сразу после минорной сборки, то в итоге вы получите одну большую длинную паузу.

Существует несколько параметров, позволяющих контролировать такое поведение: CMSScheduleRemarkEdenSizeThreshold, CMSScheduleRemarkEdenPenetration, CMSScheduleRemarkEdenSizeThreshold, CMSScheduleRemarkEdenPenetration, CMSScheduleRemarkEdenSizeThreshold, CMSScheduleRemarkEdenPenetration, CMSScheduleRemarkEdenSizeThreshold, CMSScheduleRemarkEdenPenetration, CMSSMaxAbortablePrecleanTime. Я предлагаю попробовать CMSScavengeBeforeRemark, который заставит вызывать второстепенную сборку непосредственно перед замечанием.

Таким образом, вы минимизируете время между пометкой инициализации и примечанием, и на этапе примечания будет меньше работы.

Это будет особенно эффективно, если паузы второстепенных сборок будут значительно короче реплик, что обычно и происходит.

4. Полный GC в журнале



(concurrent mode failure): 798958K->74965K(1048576K), 0.0270334 secs] 1033467K->74965K(1284544K), [CMS Perm : 3022K->3022K(21248K)], 0.0270963 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] Total time for which application threads were stopped: 0.0271630 seconds

Я уже подробно описывал этот и несколько других случаев, вызывающих Full GC. здесь .

Это все, что я хотел вам сказать о паузах.

Ах да, пожалуйста, не используйте инкрементную CMS (-XX:+CMSIncrementalMode), если у вас нет одного или двух ядер.

Просто все будет работать медленнее.

И несколько слов о других алгоритмах.

Garbage First (G1), который должен появиться по умолчанию в Java 7, и его можно включить в Java 6, начиная с Java SE 6, обновление 14, с параметрами -XX:+UnlockExperimentalVMOptions и -XX:+UseG1GC. Идея состоит в том, чтобы разделить всю область памяти на небольшие участки памяти, которые собираются в разное время, делая тем самым очень маленькие паузы.

Существуют различные параметры JVM, позволяющие задавать нужные паузы, на основании которых память разбивается на области.

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

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

В последнее время часто встречаю посты про Азул Г.

К.

, который работает вообще без пауз, независимо от топологии объекта, размера и области памяти.

Это звучит очень многообещающе, но их решение долгое время было доступно только на их собственном оборудовании (системы Azul Vega), поскольку алгоритм требует специальных инструкций LVB (барьер загруженного значения).

Хорошей новостью является то, что наконец-то можно реализовать аналогичный механизм на архитектуре процессоров Intel x86-64. Если бы я писал приложение со сверхнизкой задержкой с нуля, я бы определенно рассмотрел возможность использования этой JVM, но если ваше приложение уже находится в производстве, и его стабильность является одним из наиболее важных требований, то переключитесь с Oracle HotSpot JVM на любую еще один, довольно рискованный шаг.

Давайте вспомним, со сколькими проблемами столкнулись пользователи даже при переходе с пятой на шестую Яву.



Ссылки на тему:

  1. Официальная документация по управлению памятью в Java
  2. Официальная документация по настройке сборщика мусора
  3. Блог сотрудника Sun, работавшего над GC. Здесь он подробно описывает различные аспекты того, как работает сбор мусора.

    Настоятельно рекомендую.

  4. Часто задаваемые вопросы по различным аспектам управления памятью JVM
  5. Описание альтернативы Azul GC. Также указываются недостатки существующих решений и объясняются причины их возникновения.

Теги: #java #gc #gc #java
Вместе с данным постом часто просматривают: