На G1 крупные массивы вызывают частые паузы при умеренной заполненности heap. Как размер одного объекта может привести к такому поведению?
На G1 объект считается крупным, если его размер достигает не менее половины размера региона. Такой объект становится humongous object: он размещается непосредственно в одном или нескольких старых регионах, требует contiguous-области и обычно не перемещается обычной эвакуацией. Поэтому даже при умеренной общей занятости heap крупные массивы могут вызывать фрагментацию, давление на свободные регионы и дополнительные циклы маркировки или полную сборку мусора.
G1 использует heap, разделённый на регионы, вместо фиксированного деления на единые области young и old generation. Это позволяет выбирать регионы для сборки и лучше управлять компромиссом между пропускной способностью и длительностью пауз на больших heap.
Особенно крупные объекты плохо вписываются в модель обычной региональной эвакуации: копирование такого объекта дорого, а поиск нескольких соседних свободных регионов может быть затруднён. Для них в G1 предусмотрен отдельный путь размещения — humongous allocation.
Предположим, приложение регулярно создаёт большие byte-массивы для сериализации, загрузки файлов или сетевых буферов. Эти объекты могут занимать несколько регионов, быстро заполнять старое поколение и оставлять между занятыми областями неудобные для повторного использования фрагменты.
Если крупный объект живёт дольше ожидаемого, он удерживает целую группу регионов. Если свободных contiguous-регионов недостаточно, G1 может быть вынужден чаще запускать маркировку, выполнять менее выгодные циклы сборки или перейти к Full GC, несмотря на наличие свободной памяти суммарно.
Размер региона G1 выбирается при запуске JVM и обычно находится в диапазоне от 1 до 32 МБ. Объект размером не менее половины региона считается humongous; точная граница зависит от фактического размера региона и реализации JVM.
Такой объект размещается в старых регионах и занимает один или несколько соседних регионов. Для большого массива это означает, что учитывается не только его полезный размер, но и необходимость найти подходящий непрерывный диапазон регионов.
Humongous-объекты не проходят обычную эвакуацию вместе с большинством живых объектов young-регионов. Пока объект достижим, занятые им регионы нельзя освободить; после обнаружения недостижимости они могут быть возвращены куче в рамках механизмов G1, связанных с маркировкой и очисткой.
Проблема не сводится к проценту занятого heap. Важны размер отдельных объектов, длительность их жизни, доступность соседних регионов, темп выделения памяти и выбранные цели пауз. Поэтому увеличение heap не обязательно устраняет задержки: оно может лишь отсрочить нехватку подходящих регионов.
Увеличение размера региона повышает порог humongous-объекта и иногда уменьшает число таких размещений. Однако слишком крупные регионы снижают детализацию работы G1 и могут увеличить объём работы за отдельную паузу. Простое изменение размера heap или региона без анализа профиля распределений может ухудшить другой аспект поведения.
Для диагностики проверяют журналы GC с информацией о humongous-регионах, распределение размеров аллокаций, время жизни крупных объектов и причины начала циклов маркировки. Важно отличать краткоживущие крупные объекты от удерживаемых долгоживущих: первые создают давление на аллокатор, вторые — устойчиво занимают регионы.
Сервис формирует большие JSON-документы в памяти перед отправкой клиенту. При умеренной занятости heap в журналах G1 появляются частые сообщения о humongous-аллокациях, а отдельные паузы становятся нестабильными.
Первый вариант — увеличить heap. Плюс в том, что проблема может проявляться реже; минус — увеличивается объём памяти и не устраняется причина крупных аллокаций. Второй вариант — изменить размер региона. Это может поднять порог humongous-объектов, но одновременно изменить длительность пауз и стоимость работы с регионами.
Третий вариант — перейти на потоковую сериализацию и отправку, чтобы не создавать целый документ одним массивом. Обычно это наиболее устойчивое решение, если протокол и клиент допускают потоковую передачу: уменьшается пиковый размер объектов и снижается зависимость от contiguous-регионов. Компромисс — усложнение обработки ошибок, повторной отправки и контроля границ документа.
На практике сначала подтверждают гипотезу по GC-логам и профилю размеров объектов, затем выбирают изменение архитектуры, а параметры G1 используют как дополнительную настройку. Это позволяет устранить источник давления, а не только увеличить запас до следующего сбоя.
Как отличить humongous-объект от просто большого объекта?
Критерий задаётся не абсолютным размером объекта, а его отношением к размеру региона G1: объект размером не менее половины региона попадает в специальную категорию. Поэтому один и тот же массив может быть обычным объектом при одном размере региона и humongous-объектом при другом.
При оценке нужно учитывать фактический размер объекта, служебные накладные расходы и выбранный JVM размер региона, а не ориентироваться только на число мегабайт в исходных данных.
Почему наличие свободной памяти не гарантирует успешную humongous-аллокацию?
Для крупного объекта требуется несколько соседних свободных регионов. Свободная память может быть распределена небольшими разрозненными участками, которые суммарно достаточно велики, но не образуют нужный contiguous-диапазон.
В результате G1 может начать дополнительные действия по очистке или консолидации, а в неблагоприятном случае — выполнить Full GC. Это объясняет ситуации, когда график общей занятости heap выглядит безопасно, но крупная аллокация всё равно вызывает задержку.
Всегда ли крупные объекты нужно устранять потоковой обработкой?
Нет. Если крупный объект создаётся редко, быстро становится недостижимым и не влияет на цели пауз, потоковая обработка может добавить неоправданную сложность. Решение зависит от частоты аллокаций, времени жизни объектов, размера heap и требований к задержке.
Потоковая обработка особенно полезна при регулярных крупных аллокациях или когда данные можно передавать частями. Если же причина — неожиданное удержание объектов, сначала нужно устранить утечку ссылок: изменение формата обработки не заменяет исправление жизненного цикла данных.