Я верю, что вы знакомы с принципом Питера . В целом, принцип заключается в том, что повышение в должности может привести и приведет к ситуации, когда повышенный в должности человек больше не подходит для данной работы.
Для JVM похожая проблема существует. Слишком быстрое продвижение объектов может существенно повлиять на производительность. В этом посте мы раскрываем концепцию уровня продвижения, демонстрируем, как ее измерить, и объясняем практическую ценность концепции.
Это продолжение нашего поста с прошлой недели, которое объяснило концепцию ставки распределения .
Скорость продвижения измеряется количеством данных, передаваемых от молодого поколения к старому поколению за единицу времени . Часто измеряется в МБ / с, аналогично скорости выделения. Как и в нашем посте о ставке распределения, давайте еще раз покопаемся, чтобы узнать, как рассчитывается ставка повышения и почему вы должны заботиться о ставке вообще.
Измерение скорости продвижения
Давайте начнем с измерения скорости продвижения. Для этого включим ведение журнала GC, указав -XX: + PrintGCDetails -XX: + PrintGCTimeStamps flags для JVM. JVM теперь начинает регистрировать паузы GC, как показано в следующем фрагменте:
0.291: [GC (Allocation Failure) [PSYoungGen: 33280K->5088K(38400K)] 33280K->24360K(125952K), 0.0365286 secs] [Times: user=0.11 sys=0.02, real=0.04 secs]
0.446: [GC (Allocation Failure) [PSYoungGen: 38368K->5120K(71680K)] 57640K->46240K(159232K), 0.0456796 secs] [Times: user=0.15 sys=0.02, real=0.04 secs]
0.829: [GC (Allocation Failure) [PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(159232K), 0.0861795 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
Из вышесказанного мы можем извлечь размер Young Generation и общую кучу как до, так и после события сбора. Зная потребление молодого поколения и общую кучу, легко вычислить потребление старого поколения как просто дельту между ними. Выражая информацию в журналах GC как:
Мероприятие | Время | Молодые уменьшились | Всего уменьшилось | Повышен | Скорость продвижения |
---|---|---|---|---|---|
1-й ГК | 291ms | 28,192K | 8,920K | 19,272K | 66,2 МБ / с |
2-й ГК | 446ms | 33,248K | 11,400K | 21,848K | 140,95 МБ / с |
3-й ГК | 829ms | 66,560K | 30,888K | 35,672K | 93,14 МБ / с |
Общее | 829ms | 76,792K | 92,63 МБ / с |
позволит нам извлечь скорость продвижения за измеряемый период. Мы видим, что в среднем скорость продвижения составляла 92 МБ / с, а какое-то время она достигала 140,95 МБ / с.
Анализировать влияние
Теперь, имея определение уровня продвижения и зная, как его измерить, давайте посмотрим на практическую ценность этой информации.
Опять же, аналогично скорости распределения, основное влияние уровня продвижения является изменение частоты пауз в GC. Но в отличие от уровня распределения, который влияет на частоту незначительных событий GC , уровень продвижения влияет на частоту основных событий GC . Позвольте мне объяснить — чем больше вы продвигаете старое поколение, тем быстрее вы его заполняете. Более быстрое заполнение Old gen означает, что частота очистки событий GC увеличится.
С практической точки зрения, высокий уровень продвижения может привести к появлению симптома проблемы, называемой преждевременным продвижением . Чтобы объяснить проблему, давайте вспомним, почему куча JVM в первую очередь разделена на разные пулы памяти. Причина этого основана на наблюдениях, которые:
- Большинство объектов быстро не используются
- Те, которые обычно не выживают в течение (очень) долгого времени
Эти наблюдения объединяются в гипотезе слабых поколений . Основываясь на этой гипотезе, память внутри ВМ делится на то, что называется молодым поколением и старым (или постоянным) поколением. Наличие таких отдельных и индивидуально очищаемых областей позволяет GC применять различные алгоритмы для очистки этих областей, таким образом улучшая производительность GC.
Таким образом, преждевременное продвижение происходит, когда объекты с малой продолжительностью жизни не собираются в молодом поколении и переходят в старое поколение . Очистка таких объектов становится задачей Major GC, которая не предназначена для частых запусков и приводит к более длительным паузам GC, значительно влияющим на пропускную способность приложения.
Симптом, который сигнализирует о том, что приложение страдает от преждевременного продвижения, — это когда уровень продвижения приближается к уровню распределения . В нашем случае мы, безусловно, сталкиваемся с такой проблемой, поскольку наша скорость выделения составляет 161 МБ / с, а скорость продвижения составляет 92 МБ / с. Решение проблемы может быть таким же простым, как увеличение размера молодого поколения путем изменения параметров -XX: NewSize , -XX: MaxNewSize и -XX: SurvivorRatio .
Во многих случаях это все равно приведет к слишком частым малым прогонам GC. В такой ситуации вам нужно будет изменить приложение и снизить ставку распределения. То, как этого можно достичь, зависит от конкретного приложения, но введение кеширования для часто создаваемых объектов может быть способом решения этой проблемы.
Вынос
С практической точки зрения вам следует позаботиться о распределении и уровне продвижения, чтобы понять, насколько хорошо GC может идти в ногу с темпами создания и продвижения объектов в старое поколение. Эти факторы могут существенно повлиять на производительность вашего приложения. Эту проблему часто можно решить, используя более подходящую конфигурацию GC или просто изменив исходный код.