Сборщик мусора Java Serial работает намного лучше, чем другие сборщики мусора?

Я тестирую 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 us, средняя латентность 8,704 us, максимальная латентность 1197 us

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

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

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

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

О, и для записи я использую 64-разрядную виртуальную машину Java HotSpot TM.

Ответ 1

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

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

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

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

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

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

Ответ 2

Я не считаю это неожиданным.

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

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

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

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

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

Ответ 3

Отличная беседа инженера-щебета на конференции QCon Conference на эту тему - вы можете посмотреть его здесь.

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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