Серийный сборщик мусора в Java работает намного лучше, чем другие сборщики мусора? - PullRequest
11 голосов
/ 16 марта 2012

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

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

1) Последовательный: -XX: + UseSerialGC

2) Параллельный: -XX: + UseParallelOldGC

3) Параллельный: -XX: + UseConcMarkSweepGC

4) Параллельный / инкрементный:-XX: + UseConcMarkSweepGC -XX: + CMSIncrementalMode -XX: + CMSIncrementalPacing

Я выполнял каждую технику в течение пяти часов.Я периодически использовал список GarbageCollectorMXBean, предоставленный ManagementFactory.getGarbageCollectorMXBeans (), чтобы получить общее время, потраченное на сбор мусора.

Мои результаты?Обратите внимание, что здесь «задержка» равна «Количество времени, которое мое приложение + API потратил на обработку каждого сообщения, отключенного от сети».

Последовательный: 789 событий GC на общую сумму 1309 мс;средняя задержка 47,45 мкс, медианная задержка 8,704 мкс, максимальная задержка 1197 мкс

Параллельно: события 1715 GC на общую сумму 122518 мс;средняя задержка 450,8 мкс, средняя задержка 8,448 мкс, максимальная задержка 8292 мкс

Одновременно: 4629 событий GC на общую сумму 116229 мс;средняя задержка 707,2 мкс, средняя латентность 9,216 мкс, максимальная задержка 9151 мкс

Инкремент: 5066 событий GC на общую сумму 200213 мс;средняя задержка 515,9 мкс, средняя задержка 9,472 мкс, максимальная задержка 14209 мкс

Я считаю эти результаты настолько невероятными, что они граничат с абсурдом.Кто-нибудь знает, почему у меня могут быть такие результаты?

О, и для записи, я использую Java HotSpot (TM) 64-битный сервер VM.

Ответы [ 5 ]

18 голосов
/ 16 марта 2012

Я работаю над приложением Java, которое, как ожидается, максимизирует пропускную способность и минимизирует задержку

Две проблемы с этим:

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

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

// Avoid generating any garbage
Thread.sleep(10000000);

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

4 голосов
/ 16 марта 2012

Я совсем не нахожу это удивительным.

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

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

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

Теперь, почему я говорю, что это, вероятно, незначительный вкладчик в этом случае? Это довольно просто: серийный сборщик израсходовал чуть более секунды из пяти часов. Несмотря на то, что в течение этих ~ 1,3 секунд больше ничего не было сделано, это такой небольшой процент из пяти часов, что он, вероятно, не сильно (если вообще имел) реальную разницу в вашей общей пропускной способности.

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

2 голосов
/ 16 марта 2012

На конференции QCon Conference 2012 была прекрасная беседа инженера Twitter по этой теме - вы можете посмотреть ее здесь .

В нем обсуждались различные «поколения» в памяти и сборке мусора Hotspot JVM (Eden, Survivor, Old). В частности, обратите внимание, что «Concurrent» в ConcurrentMarkAndSweep применяется только к старому поколению, то есть к объектам, которые некоторое время тусуются.

Короткоживущие объекты - это GCd из поколения «Eden» - это дешево, но это событие «останови мир» GC независимо от того, какой алгоритм GC вы выбрали!

Совет состоял в том, чтобы сначала настроить молодое поколение, например. выделите много нового Эдема, чтобы у юношей было больше шансов умереть молодым и стать дешевле. Используйте + PrintGCDetails, + PrintHeapAtGC, + PrintTenuringDistribution ... Если вы получили более 100% выживших, значит, места не осталось, поэтому объекты быстро переводятся в "Старый" - это плохо.

При настройке старого поколения, если задержка является высшим приоритетом, рекомендуется сначала попробовать ParallelOld с автонастройкой (+ AdaptiveSizePolicy и т. Д.), Затем попробовать CMS, а затем, возможно, новый G1GC.

0 голосов
/ 21 апреля 2015

При серийном сборе происходит только одно. Например, даже если несколько процессоров доступно, только один используется для выполнения сбора. Когда используется параллельный сбор, задача сборка мусора разбивается на части и эти части выполняются одновременно, на разных ЦП. Одновременная операция позволяет выполнять сбор быстрее, за счет некоторая дополнительная сложность и потенциальная фрагментация.

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

0 голосов
/ 16 марта 2012

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

но если вы хотите максимизировать пропускную способность и минимизировать задержку: GC - ваш враг! вам вообще не следует вызывать GC, а также пытаться запретить JVM вызывать GC.

используйте последовательный порт и используйте пулы объектов.

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