Сокращение времени паузы JVM> 1 секунда с использованием UseConcMarkSweepGC

Я запускаю приложение с интенсивной памятью на машине с 16 ГБ ОЗУ и 8-ядерным процессором, а Java 1.6 все работает на выпуске CentOS 5.2 (Final). Сведения о точном JVM:

java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)

Я запускаю приложение со следующими параметрами командной строки:

java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ...

Мое приложение предоставляет API JSON-RPC, и моя цель - отвечать на запросы в течение 25 мс. К сожалению, я вижу задержки до 1 секунды и выше, и, похоже, это вызвано сбором мусора. Вот некоторые из более длинных примеров:

[GC 4592788K->4462162K(10468736K), 1.3606660 secs]
[GC 5881547K->5768559K(10468736K), 1.2559860 secs]
[GC 6045823K->5914115K(10468736K), 1.3250050 secs]

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

Вот несколько типичных примеров (все они были созданы в течение нескольких секунд):

[GC 3373764K->3336654K(10468736K), 0.6677560 secs]
[GC 3472974K->3427592K(10468736K), 0.5059650 secs]
[GC 3563912K->3517273K(10468736K), 0.6844440 secs]
[GC 3622292K->3589011K(10468736K), 0.4528480 secs]

Дело в том, что я думал, что UseConcMarkSweepGC избежит этого или, по крайней мере, сделает его крайне редким. Напротив, задержки, превышающие 100 мс, происходят почти один раз в минуту или более (хотя задержки более 1 секунды значительно реже, возможно, каждые 10 или 15 минут).

Другое дело, что я думал, что только FULL GC вызовет приостановку потоков, но они, похоже, не являются полными GC.

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

Вам будет очень благодарна любая помощь или совет.

Ответ 1

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

Я решил это, установив параметр Linux "swappiness" равным 0 (чтобы он не заменил данные на диск).

Ответ 2

Во-первых, ознакомьтесь с документацией Java SE 6 HotSpot [tm] Virtual Machine Garbage Collection Tuning, если вы еще этого не сделали. В этой документации говорится:

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

и немного позже...

Параллельный коллектор дважды приостанавливает приложение в течение параллельный цикл сбора.

Я замечаю, что эти GC, похоже, не освобождают очень много памяти. Возможно, многие из ваших объектов долговечны? Возможно, вы захотите настроить размеры генерации и другие параметры GC. 10 Gig - огромная куча по многим стандартам, и я бы наивно ожидал, что GC займет больше времени с такой огромной кучей. Тем не менее, 1 секунда - это очень длительное время паузы и указывает, что что-то не так (ваша программа генерирует большое количество ненужных объектов или создает объекты с трудным восстановлением или что-то еще), или вам просто нужно настроить GC.

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

Как уже говорили другие, вам нужно профилировать ваше приложение, чтобы узнать, где находится узкое место. Является ли ваш PermGen слишком большим для пространства, выделенного для него? Вы создаете ненужные объекты? jconsole работает, по крайней мере, для отображения минимальной информации о виртуальной машине. Это отправная точка. Однако, как указывали другие, вам, скорее всего, нужны более продвинутые инструменты, чем это.

Удачи.

Ответ 3

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

Считайте также настройку -XX:NewRatio. По умолчанию используется значение 1: 2, что означает, что одна треть кучи выделена для нового поколения. Для большой кучи это почти всегда слишком много. Возможно, вы захотите попробовать что-то вроде 9, которое будет содержать 9 Gb вашей кучи Gb для 10-го поколения для старого поколения.

Ответ 4

Вот некоторые вещи, которые я нашел, которые могут быть значительными.

  • JSON-RPC может генерировать множество объектов. Не так много, как XML-RPC, но все равно что-то, на что можно обратить внимание. В любом случае вы, как представляется, генерируете столько же на 100 МБ объектов в секунду, что означает, что ваш GC работает с высоким процентом времени и, вероятно, будет добавлять вашу случайную задержку. Несмотря на то, что GC одновременно, ваше оборудование/ОС, скорее всего, проявит неидеальную случайную задержку при загрузке.
  • Посмотрите на свою архитектуру банка памяти. В Linux команда numactl --hardware. Если ваша виртуальная машина будет разделена на несколько банков памяти, это значительно увеличит время вашего GC. (Это также замедлит ваше приложение, поскольку эти обращения могут быть значительно менее эффективными). Чем сложнее работать подсистема памяти, тем более вероятно, что ОС придется перемещать память вокруг (часто в больших количествах), и в результате вы получаете резкие паузы ( 100 мс не удивительно). Не забывайте, что ваша ОС делает больше, чем просто запускает ваше приложение.
  • Рассмотрим уплотнение/уменьшение потребления памяти в вашем кеше. Если вы используете несколько ГБ кеша, стоит посмотреть, как сократить потребление памяти дальше, чем вы уже сделали.
  • Я предлагаю вам одновременно профилировать ваше приложение с трассировкой выделения памяти и выборкой процессора. Это может дать очень разные результаты и часто указывает на причину таких проблем.

Используя эти подходы, латентность вызова RPC может быть уменьшена до ниже 200 микросекунд, а время GC уменьшено до 1-3 мс, производя менее 1/300 вызовов.

Ответ 5

Некоторые места для начала поиска:

Также я бы запускал код через профилировщик. Мне нравится тот, что есть в NetBeans, но есть и другие. Вы можете просмотреть поведение gc в режиме реального времени. Визуальная ВМ также делает это... но я еще не запускал ее (искал причину... но еще не имел времени или необходимости).

Ответ 6

Я также предлагаю GCViewer и профайлер.

Ответ 7

Несколько вещей, которые, я надеюсь, могут помочь:

Мне никогда не приходилось много удачи с ConcurrentCollector, теоретически он жертвует пропускной способностью для увеличения латентности, но я нашел больше удачи в сборщике пропускной способности для пропускной способности и латентности (с настройкой и для мои приложения).

Ваш Cache of Soft References - это немного опасная идея для Generation Collectors, и, вероятно, это одна из причин, почему ваши коллекции молодого поколения не собирают слишком много мусора.

Если я не ошибаюсь, независимо от того, насколько непродолжителен объект Object, если он попадает в кеш (который, несомненно, попал в Tenured Generation), он будет жив, пока не произойдет FullGC, даже если другие ссылки на него не существуют!

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

Вроде бы парадоксально, как кеширование может сократить выделение объектов, но увеличивает время GC.

Вы также можете попытаться настроить коэффициент выживаемости, он может быть слишком мал, переполняя еще более "молодые" объекты в поколение поколений.

Ответ 8

Я лично не использовал такую ​​огромную кучу, но у меня была очень низкая задержка в целом, используя следующие ключи для Oracle/Sun Java 1.6.x:

-Xincgc -XX:+UseConcMarkSweepGC -XX:CMSIncrementalSafetyFactor=50
-XX:+UseParNewGC
-XX:+CMSConcurrentMTEnabled -XX:ConcGCThreads=2 -XX:ParallelGCThreads=2
-XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=5
-XX:GCTimeRatio=90 -XX:MaxGCPauseMillis=20 -XX:GCPauseIntervalMillis=1000

Важными компонентами являются, на мой взгляд, использование CMS для поколенного поколения и ParNewGC для молодого поколения. Кроме того, это добавляет довольно большой коэффициент безопасности для CMS (по умолчанию - 10% вместо 50%) и запрашивает короткие периоды паузы. Поскольку вы настроите время отклика на 25 мс, я попробую установить -XX:MaxGCPauseMillis на еще меньшее значение. Вы даже можете попытаться использовать более двух ядер для параллельного GC, но я бы предположил, что это не стоит использования ЦП.

Вероятно, вы также можете проверить HotSpot JVM GC cheat sheet.