Рассмотрите молодую сборку, после которой в heap ещё много свободной памяти. Почему она всё равно может зав...

Рассмотрите молодую сборку, после которой в heap ещё много свободной памяти. Почему она всё равно может завершиться Full GC?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Молодая сборка может завершиться Full GC, если выжившие объекты нельзя разместить в области назначения, обычно в старом поколении. Общий объём свободной памяти не гарантирует наличие подходящего места в нужном поколении или регионе: важны организация heap, фрагментация и требования конкретного сборщика.

Исторический контекст

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

Такой подход уменьшает стоимость типичной сборки: вместо анализа всего heap JVM обрабатывает преимущественно область, где ожидается много мусора. Обратная сторона — необходимость перемещать выжившие объекты и находить для них место в другой области памяти.

Постановка проблемы

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

Когда такого пространства недостаточно, продвижение завершается неудачей. Последствиями могут стать Full GC, более длинная пауза, временное снижение пропускной способности и, если память действительно исчерпана, OutOfMemoryError.

Подробное решение

Свободная память в heap не является единой неструктурированной областью. Часть её может находиться в молодом поколении, быть зарезервированной под другие цели или не подходить для размещения продвигаемых объектов из-за фрагментации.

При классических поколенческих сборщиках проблема обычно называется promotion failure: после молодой сборки выжившие объекты не помещаются в старое поколение. JVM может запустить более полный сборщик, который дополнительно обработает старое поколение, уплотнит объекты или иным способом освободит место.

У G1 близкая ситуация называется evacuation failure. Объект может не быть перемещён в целевой регион, например при нехватке подходящих регионов; тогда G1 временно оставляет его на месте и учитывает это при последующей работе. Поэтому нельзя механически переносить термин promotion failure на все сборщики.

Диагностировать проблему следует по динамике occupancy старого поколения, размерам и длительности пауз, объёму promotion, причинам запуска Full GC и скорости распределения объектов. Одного факта «heap заполнен не полностью» недостаточно.

Возможные меры зависят от причины:

  • увеличить доступный heap, если живых объектов действительно стало больше;
  • уменьшить всплески временных объектов и размер молодого поколения, если они вызывают резкий promotion pressure;
  • проверить настройки и поведение выбранного сборщика;
  • исследовать удерживаемые объекты, если старое поколение не освобождается из-за утечки.

Увеличение heap не всегда лечит проблему: оно может лишь отсрочить её, а слишком большой heap способен увеличить стоимость полной обработки памяти.

Ситуация из практики

В сервисе редкие Full GC появляются после пиков нагрузки. Heap dump не показывает утечку, но журналы GC показывают резкий рост объёма объектов, переживающих молодые сборки, и почти заполненное старое поколение.

Рассматриваются два варианта. Простое увеличение heap даёт запас, но не устраняет кратковременный всплеск promotion и увеличивает объём памяти, который потенциально придётся обрабатывать. Уменьшение молодого поколения снижает объём объектов, одновременно продвигаемых за одну сборку, но может повысить частоту молодых пауз.

Рациональное решение — сначала подтвердить причину по GC-логам и профилю выживших объектов, затем подобрать размер поколений или параметры текущего сборщика под фактическую нагрузку. Если проблема вызвана именно всплеском временных объектов, такой подход устраняет Full GC без маскировки симптома простым увеличением heap.

Что кандидаты часто упускают

  1. Разве свободный объём heap не означает, что объект можно разместить?

Нет. Важны не только суммарные свободные байты, но и их расположение, принадлежность к поколениям или регионам, требования к размеру объекта и политика конкретного сборщика. Для продвижения объекту может требоваться место именно в старом поколении, тогда как свободная память находится в молодом.

  1. Всегда ли Full GC означает утечку памяти?

Нет. Full GC может быть следствием временного всплеска выживших объектов, неудачного продвижения, фрагментации или недостаточного запаса памяти. Утечка вероятна, если после завершения обычной рабочей нагрузки живой набор объектов продолжает расти и удерживается постоянными корнями, но это нужно подтверждать анализом heap dump и динамики GC.

  1. Почему увеличение размера молодого поколения может ухудшить ситуацию?

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