Расширяя наш испытательный стенд для улучшения детектора проблем Plumbr GC , я написал небольшой тестовый пример, который, как мне показалось, может быть интересен для более широкой аудитории. Цель, которую я преследовал, состояла в том, чтобы проверить самоадаптивность JVM в отношении того, как сегментируется куча между eden, оставшимся в живых и постоянным пространством.
Сам тест генерирует объекты партиями. Пакеты создаются один раз в секунду, и каждый пакет имеет размер 500 КБ. На эти объекты ссылаются в течение пяти секунд, после чего ссылки удаляются, и объекты из этого конкретного пакета могут собираться мусором.
Тест был запущен с Oracle Hotspot 7 JVM в Mac OS X с использованием ParallelGC и ему предоставлено 30 МБ кучи для работы. Зная платформу, мы можем ожидать, что JVM запустится со следующей конфигурацией кучи:
- JVM будет начинаться с 10 МБ в Young и 20 МБ в пространстве Tenured, поскольку без явной настройки JVM использует соотношение 1: 2 для распределения кучи между пространствами Young и Tenured.
- В моей Mac OS X 10 МБ молодого пространства далее распределяется между Eden и двумя пространствами Выжившего, учитывая 8 МБ и 2×1 МБ соответственно. Опять же, это используемые по умолчанию платформы.
Действительно, когда мы запускаем тест и заглядываем под капот с помощью jstat , мы видим следующее, подтверждая наши оценки «за спиной»:
1
2
3
4
5
6
7
8
|
My Precious:gc-pressure me$ jstat -gc 2533 1s S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 1024.0 1024.0 0.0 0.0 8192.0 5154.4 20480.0 0.0 21504.0 2718.9 0 0.000 0 0.000 0.000 1024.0 1024.0 0.0 0.0 8192.0 5502.1 20480.0 0.0 21504.0 2720.1 0 0.000 0 0.000 0.000 1024.0 1024.0 0.0 0.0 8192.0 6197.5 20480.0 0.0 21504.0 2721.0 0 0.000 0 0.000 0.000 1024.0 1024.0 0.0 0.0 8192.0 6545.2 20480.0 0.0 21504.0 2721.2 0 0.000 0 0.000 0.000 1024.0 1024.0 0.0 0.0 8192.0 7066.8 20480.0 0.0 21504.0 2721.6 0 0.000 0 0.000 0.000 1024.0 1024.0 0.0 0.0 8192.0 7588.3 20480.0 0.0 21504.0 2722.1 0 0.000 0 0.000 0.000 |
Отсюда мы также можем дать следующий набор прогнозов о том, что произойдет:
- 8 МБ в Eden будут заполнены примерно за 16 секунд — помните, мы генерируем 500 КБ объектов в секунду
- В каждый момент времени у нас есть приблизительно 2,5 МБ живых объектов — генерируя 500 КБ каждую секунду и сохраняя ссылки на объекты в течение пяти секунд, мы получаем примерно это число
- Незначительный сборщик мусора срабатывает всякий раз, когда Eden заполнен — это означает, что мы должны видеть второстепенный сборщик данных каждые 16 секунд или около того
- После незначительного сборщика мусора мы закончим преждевременное повышение — места Survivor занимают всего 1 МБ, а живой набор объемом 2,5 МБ не поместится ни в одно из наших пространств 1 МБ Survivor. Таким образом, единственный способ очистить Eden — это распространение 1,5 МБ (2,5 МБ-1 МБ) живых объектов, не помещающихся в Survivor, в пространство Tenured.
Проверка журналов также дает нам уверенность в следующих прогнозах:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
My Precious:gc-pressure me$ jstat -gc -t 2575 1s Time S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 6.6 1024.0 1024.0 0.0 0.0 8192.0 4117.9 20480.0 0.0 21504.0 2718.4 0 0.000 0 0.000 0.000 7.6 1024.0 1024.0 0.0 0.0 8192.0 4639.4 20480.0 0.0 21504.0 2718.7 0 0.000 0 0.000 0.000 ... cut for brevity ... 14.7 1024.0 1024.0 0.0 0.0 8192.0 8192.0 20480.0 0.0 21504.0 2723.6 0 0.000 0 0.000 0.000 15.6 1024.0 1024.0 0.0 1008.0 8192.0 963.4 20480.0 1858.7 21504.0 2726.5 1 0.003 0 0.000 0.003 16.7 1024.0 1024.0 0.0 1008.0 8192.0 1475.6 20480.0 1858.7 21504.0 2728.4 1 0.003 0 0.000 0.003 ... cut for brevity ... 29.7 1024.0 1024.0 0.0 1008.0 8192.0 8163.4 20480.0 1858.7 21504.0 2732.3 1 0.003 0 0.000 0.003 30.7 1024.0 1024.0 1008.0 0.0 8192.0 343.3 20480.0 3541.3 21504.0 2733.0 2 0.005 0 0.000 0.005 31.8 1024.0 1024.0 1008.0 0.0 8192.0 952.1 20480.0 3541.3 21504.0 2733.0 2 0.005 0 0.000 0.005 ... cut for brevity ... 45.8 1024.0 1024.0 1008.0 0.0 8192.0 8013.5 20480.0 3541.3 21504.0 2745.5 2 0.005 0 0.000 0.005 46.8 1024.0 1024.0 0.0 1024.0 8192.0 413.4 20480.0 5201.9 21504.0 2745.5 3 0.008 0 0.000 0.008 47.8 1024.0 1024.0 0.0 1024.0 8192.0 961.3 20480.0 5201.9 21504.0 2745.5 3 0.008 0 0.000 0.008 |
Не через 16 секунд, а, скорее, примерно через каждые 15 секунд, запускается сборка мусора, очищается Eden и распространяется ~ 1 МБ живых объектов в одно из пространств Survivor и переполняется остальное в старое пространство.
Все идет нормально. JVM ведет себя так, как мы ожидаем. Интересная часть начинается после того, как JVM некоторое время контролирует поведение GC и начинает понимать, что происходит. Во время нашего теста это происходит примерно за 90 секунд:
01
02
03
04
05
06
07
08
09
10
11
12
|
My Precious:gc-pressure me$ jstat -gc -t 2575 1s Time S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 94.0 1024.0 1024.0 0.0 1024.0 8192.0 8036.8 20480.0 8497.0 21504.0 2748.8 5 0.012 0 0.000 0.012 95.0 1024.0 3072.0 1024.0 0.0 4096.0 353.3 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 96.0 1024.0 3072.0 1024.0 0.0 4096.0 836.6 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 97.0 1024.0 3072.0 1024.0 0.0 4096.0 1350.0 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 98.0 1024.0 3072.0 1024.0 0.0 4096.0 1883.5 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 99.0 1024.0 3072.0 1024.0 0.0 4096.0 2366.8 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 100.0 1024.0 3072.0 1024.0 0.0 4096.0 2890.2 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 101.0 1024.0 3072.0 1024.0 0.0 4096.0 3383.7 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 102.0 1024.0 3072.0 1024.0 0.0 4096.0 3909.7 20480.0 10149.6 21504.0 2748.8 6 0.014 0 0.000 0.014 103.0 3072.0 3072.0 0.0 2720.0 4096.0 323.0 20480.0 10269.6 21504.0 2748.9 7 0.016 0 0.000 0.016 |
Здесь мы видим удивительную адаптивность JVM. Узнав о поведении приложения, JVM изменила размер оставшегося в живых пространства, чтобы быть достаточно большой, чтобы вместить все живые объекты. Новая конфигурация для пространства Юнга теперь:
- Eden 4MB
- Survivor пространства 3MB каждый
После этого частота ГХ увеличивается — Eden теперь на 50% меньше, и вместо ~ 16 секунд он заполняется примерно за 8 секунд или около того. Но выгода также видна, поскольку места для выживших теперь достаточно велики, чтобы в них можно было разместить живые объекты в любой момент времени. В сочетании с тем фактом, что никакие объекты не живут дольше, чем один минорный цикл ГХ (помните, что в любой момент времени всего 2,5 МБ живых объектов), мы прекращаем продвижение объектов в старое пространство.
Продолжая следить за JVM, мы видим, что старое использование пространства остается неизменным после принятия. Больше нет объектов, распространяемых на старые, но поскольку не запускается ни один крупный сборщик мусора, ~ 10 МБ мусора, который удалось распространить до того, как произошла адаптация, будет жить в старом пространстве вечно.
Вы также можете отказаться от «удивительной адаптивности», если уверены в том, что делаете. Указание -XX-UseAdaptiveSizingPolicy в параметрах JVM заставит JVM придерживаться параметров, заданных во время запуска, и не пытаться перехитрить вас. Используйте эту опцию с осторожностью, современные JVM, как правило, действительно способны предсказать подходящую конфигурацию для вас.
Ссылка: | Размер адаптивной кучи от нашего партнера JCG Никиты Сальникова Тарновского в блоге Plumbr Blog . |