Сокращение времени паузы JVM> 1 секунды с использованием UseConcMarkSweepGC - PullRequest
10 голосов
/ 22 февраля 2009

Я запускаю приложение с интенсивным использованием памяти на компьютере с 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 минут).

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

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

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

Ответы [ 8 ]

11 голосов
/ 22 февраля 2009

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

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

11 голосов
/ 22 февраля 2009

Сначала ознакомьтесь с документацией по настройке сборки мусора виртуальной машины Java SE 6 HotSpot [tm], если вы этого еще не сделали. Эта документация гласит:

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

и чуть позже ...

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

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

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

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

Удачи.

6 голосов
/ 07 марта 2009

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

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

2 голосов
/ 22 февраля 2009

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

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

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

0 голосов
/ 17 января 2012

Лично я не использовал такую ​​огромную кучу, но в целом у меня очень низкая задержка при использовании следующих переключателей для 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 .

0 голосов
/ 25 февраля 2009

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

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

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

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

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

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

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

0 голосов
/ 22 февраля 2009

Я бы также предложил GCViewer и профилировщик.

0 голосов
/ 22 февраля 2009

Некоторые места, чтобы начать искать:

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

...