Профилировщик показывает, что после mixed GC в G1 часть старого поколения всё ещё занята недостижимыми объектами. Какой механизм объясняет их временное сохранение?
G1 может временно не освобождать такие объекты из-за алгоритма конкурентной маркировки SATB — Snapshot-At-The-Beginning. Текущий цикл маркировки фиксирует состояние достижимости на определённый момент, поэтому объект, ставший недостижимым уже после этого момента, может считаться живым до следующего цикла маркировки.
Полностью последовательная маркировка большого heap приводит к длинным паузам приложения. Конкурентная маркировка появилась, чтобы выполнять основную часть анализа достижимости параллельно с Java-потоками, оставляя короткими только необходимые фазы остановки.
G1 использует региональную организацию heap и выбирает для mixed GC наиболее выгодные старые регионы. Для корректной конкурентной маркировки он применяет SATB: сборщик сохраняет логическую картину heap на начало цикла, даже если приложение параллельно меняет ссылки.
После mixed GC инженер может увидеть в heap dump или профиле объекты, на которые уже нет обычных ссылок. Ошибочно считать, что G1 обязан немедленно вернуть всю память, занимаемую такими объектами, или что сборщик работает некорректно.
Такое ожидание приводит к преждевременной настройке heap, принудительным полным сборкам и неверной диагностике утечки. Кроме того, heap dump, снятый между фазами маркировки, отражает текущее состояние объектов, а не обязательно решение текущего цикла маркировки.
В начале цикла конкурентной маркировки G1 формирует снимок логической достижимости. Если Java-код удаляет ссылку на объект после этого момента, SATB-барьер сохраняет информацию, необходимую для того, чтобы объект, живший в начальном снимке, не был ошибочно признан недостижимым слишком рано.
Поэтому объект, который стал недостижимым во время текущего цикла, может попасть в число «живых» для этого цикла. Он обычно будет утилизирован только после следующего цикла маркировки, когда его отсутствие среди актуальных корней и ссылок будет подтверждено.
После завершения маркировки G1 выполняет mixed GC, выбирая старые регионы с большим количеством мусора с учётом целевого времени паузы. Mixed GC не обязан обрабатывать все старые регионы сразу: регион может быть отложен из-за выбранного бюджета паузы, низкой эффективности или особенностей перемещения объектов.
На результат также влияют remembered sets, необходимость эвакуации и невозможность временно переместить некоторые объекты. Поэтому наличие занятой памяти сразу после одной mixed GC само по себе не доказывает утечку.
Проверять гипотезу следует по логам фаз G1, последовательности циклов маркировки и динамике после нескольких последующих сборок. Если память стабилизируется после завершения новых циклов, это нормальная задержка обнаружения и освобождения. Если объём живых объектов растёт от цикла к циклу, нужно отдельно искать реальные корни удержания — кэши, очереди, статические поля или долгоживущие графы объектов.
Сервис после пикового трафика сохраняет в heap большое число временных объектов. Сразу после mixed GC heap dump показывает, что часть старых регионов заполнена объектами, которые уже не используются.
Вариант с частым вызовом полного GC может быстро вернуть память, но создаёт длинные глобальные паузы и маскирует причину. Агрессивное уменьшение целевого времени паузы также может ухудшить освобождение старого поколения: G1 будет обрабатывать меньше регионов за одну паузу.
Команда сначала проверяет логи конкурентных циклов и сравнивает объём живых данных после нескольких mixed GC. Затем она ждёт завершения следующего цикла маркировки и одновременно проверяет удерживающие ссылки в профилировщике.
Если после нового цикла объём живых объектов возвращается к исходному уровню, настройку не меняют: задержка объясняется SATB и расписанием mixed GC. Если же объём продолжает расти, находят долгоживущий корень — например, неограниченный кэш — и устраняют его, потому что настройка G1 не исправит утечку.
Нет. Heap dump показывает состояние графа объектов в момент снимка, а текущий цикл маркировки может использовать более ранний SATB-снимок. Кроме того, конкретный регион мог ещё не попасть в mixed GC из-за ограничений паузы или стратегии выбора регионов.
При изменении ссылки барьер сохраняет информацию о старом значении, если это необходимо для текущего снимка. Благодаря этому объект, который был достижим в начале маркировки, не теряется из анализа только потому, что приложение изменило граф во время конкурентной фазы.
Цена такой корректности — временное переоценивание живости: некоторые уже ненужные объекты остаются до следующего цикла. Это компромисс в пользу коротких пауз и конкурентной работы сборщика.
Если после завершения последующих циклов маркировки объём живых объектов продолжает устойчиво расти, одной задержки SATB недостаточно. Нужно проверять реальные цепочки удержания от GC Roots, незавершённые очереди обработки, кэши, статические ссылки и другие долгоживущие структуры.
Дополнительно анализируют, какие регионы G1 выбирает для mixed GC, не ограничивает ли обработку целевое время паузы и нет ли проблем с эвакуацией. Вывод делают по динамике нескольких циклов и логам, а не по одному heap dump.