惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как Monium приручил GC: разбираемся со сборщиками мусора в observability‑платформе
raipc (Yande · 2026-05-06 · via Все публикации подряд на Хабре

Всем привет, меня зовут Антон Рыбочкин, я старший разработчик бэкенда в команде Yandex Monium. Monium — это платформа для сбора, хранения и анализа телеметрии: метрик, логов и трейсов. Она позволяет дать оценку того, как себя чувствует сервис, находить причины сбоев, оперативно уведомлять об аномалиях. 

Изначально эта платформа развивалась как внутренняя система для мониторинга сервисов в масштабах всего Яндекса. Отсюда высокие требования к надёжности — телеметрия должна быть доступна, даже когда другие сервисы лежат. Так что высокие нагрузки нам привычны: на Хабре мы уже рассказывали про опыт сбора 1 000 000 метрик в секунду с сетевых устройств. И с точки зрения бэкенда в таких кейсах есть свои вызовы, один из них — сборка мусора, или сокращённо GC.

Сервисы Monium Metrics для сбора и визуализации метрик — важная часть платформы. По большей части они написаны на Java и занимаются парсингом временных рядов во внутреннее представление, их агрегацией и хранением в YDB

В этой статье я расскажу про наш опыт с разными сборщиками мусора: с какими проблемами Java GC мы столкнулись в разных сервисах, как их можно диагностировать и как решить. Эксперименты проводились в конце 2024 — начале 2025 года. На большинстве сервисов мы используем свежую версию Java (23 на момент экспериментов), за исключением некоторых с отличающимся графиком релизов — они используют LTS‑версию Java (в данном случае 21).

История первая: про ZGC и распухшие объекты

Итак, у нас были: 

  • Java 23

  • Несколько тысяч нод в кластере

  • Хип 75 ГБ

  • Allocation rate ~15 Гб/с на ноде 

  • ParallelGC 

  • FullGC паузы до 30 с 

  • Хиккапы 1 с в p99.99

Когда мы внедряли покомпонентные SLO, то решили измерить, насколько паузы GC приводят к задержкам ответов на читающие запросы. Для кросс‑шардовых запросов (например, агрегации по разным сервисам), это оказывалось особенно чувствительно. В этом случае результат не будет собран, пока не получен ответ со всех запрашиваемых хостов, а GC на одном хосте просаживал тайминги или вовсе таймаутил запрос. Паузы влияют как на читающие запросы, так и на пишущие, и ошибка таймаута возвращается клиенту, который должен знать, можно ли безопасно ретраить запрос (что в большинстве случаев не так, если применяется агрегация на записи).

Мы решили протестировать разные сборщики мусора с целью исправить проблему. Для экспериментов взяли G1, Shenandoah и Generational ZGC.

  1. Shenandoah постоянно потреблял выше 35% CPU. 

  2. G1 стабильно работал с таргетом в 1 с, но с меньшим — часто уходил в FullGC и тратил значительно больше CPU. 

  3. Лучше всех себя показал ZGC — минимальные паузы, но потребление CPU выше (~7%) чем в G1 с таргетом в 1 с.

На графике: хиккапы упали с 1 с до 15 мс в p99.99, потребление CPU выросло на 7%

На графике: хиккапы упали с 1 с до 15 мс в p99.99, потребление CPU выросло на 7%

Однако при переходе на ZGC мы столкнулись с неожиданным поведением. Приборы показывали, что потребляемая память возросла в 17 раз, а батчер внутри приложения перестал работать и стал грузить базу маленькими запросами, так как размер каждого элемента в батче мнимо увеличился на 256 КБ. 

Смена сборщика мусора сломала код, полагающийся на адресную арифметику: мы используем библиотеку JOL, чтобы вычислять размеры объектов и динамически рассчитывать, сколько памяти используется структурами данных в конкретных шардах. JOL применяется для анализа внутренней структуры объектов Java, включая вычисление размера, вывод порядка полей объекта в памяти, определение используемых оптимизаций (Compressed Pointers, Lilliput).

Балансировщик с этой информацией может корректно селить шарды по хостам и укладываться в лимиты. 

В Java сборщик мусора отвечает не только за аллокацию памяти и сборку мусора, но и за разметку объектов (по каким адресам может располагаться начало объекта, что хранится в заголовке, как кодируются ссылки). Многие знают, за счёт чего работают сжатые указатели и как из них получить нормальный адрес, однако в Generational ZGC появился принципиально новый подход к кодированию адреса, который пока не все инструменты понимают.

Какие были варианты это исправить:

  • Вернуться к G1. 

  • Переписать расчёт размера объектов. 

  • Починить JOL. 

Наше решение 

Мы решили заимплементить полноценную поддержку адресов ZGC в апстрим JOL.

Подробнее про JOL и представление адресов в ZGC

Если запускать JOL под Linux или MacOS без прав суперпользователя и с включённым Generational ZGC, то вычисляемый размер объектов будет значительно выше ожидаемого из‑за некорректного значения выравнивания. Это вызвано двумя багами:

  1. При некоторых условиях (без прав суперпользователя под Linux или MacOS) JOL не читает значение выравнивания объектов из VMOptions, несмотря на то, что такая информация предоставляется виртуальной машиной.

  2. JOL не имеет логики для нормализации адресов ZGC наподобие той, что реализована для сжатых указателей (‑XX:+UseCompressedOops).

При инициализации JOL вычисляет выравнивание эвристически, аллоцируя множество объектов, читает то, что виртуальная машина хранит в качестве адресов, нормализует (в случае необходимости домножает сжатые указатели), вычисляет дельты, и возвращает в качестве результата наибольший общий делитель.

В старой версии ZGC метаданные («цвета») хранятся в старших битах и три виртуальных адреса маппятся на один физический, заставляя нервничать тулы диагностики в Linux. В отличие от этого в Generational ZGC используется 12 бит «цвета» (и 4 неиспользуемых бита), они хранятся в младших битах адреса, и трансляция адреса происходит путём побитового сдвига на каждом чтении. Из‑за нестандартного кодирования адреса и включения дополнительных метаданных в Generational ZGC невозможна реализация сжатых указателей.
Цвета кодируются следующим образом в виде 12 бит: RRRRMMmmFFrr — используемые биты чередуются в зависимости от чётности цикла сборки мусора.

  • Remapped (RRRR) определяет, был ли объект перемещён. Если маска не равна одной из валидных, нужно сходить в forwarding table и обновить ссылку, так как объект был перемещён.

  • Marked (MM, mm) — ставятся, когда объект достижим в текущем цикле young gen‑ или oldgen‑сборки.

  • Finalizable (FF) означает, что объект достижим только финализатором.

  • Remembered (rr) помечает объект, который должен быть добавлен в RemSet для отслеживания ссылок из объекта старого поколения в объект нового поколения.

После патчинга JOL мы смогли запустить наш сервис уже с ZGC. Метрики потребляемой памяти нормализовались. Хиккапы упали до 15 мс p99.99.

Тайминги ответов сервера сократились вдвое: 

Тайминги кросс‑шардовых запросов также упали на 20–50%

Немного дёгтя про сложности мониторинга

Generational ZGC работает конкурентно с потоками приложения, за свой цикл совершая три STW‑паузы длительностью десятки микросекунд. Если потоку приложения не удаётся аллоцировать память под новый объект, ZGC приостанавливает аллоцирующий поток, пока не освободится место. Это создаёт сложности для мониторинга реальных пауз: ZGC не репортит по JMX события пейсинга (приостановки) потоков. 

Ориентир может дать jHiccup, однако реальное время пауз GC может быть: 

  • ниже, если хиккапы вызваны перегрузкой по CPU в системе, 

  • выше, если jHiccup работает в отдельном потоке, который может избежать приостановки из‑за невысокого рейта аллокаций. 

Пейсинг сработает, если нужно аллоцировать новый TLAB. Но jHiccup аллоцирует один объект за цикл, и этого может не хватить. 

Более точные метрики даёт JFR. Однако если вы используете OpenTelemetry для сбора метрик, необходимо реализовать логику сбора самостоятельно, так как библиотека opentelemetry‑java‑instrumentation поддерживает только метрики JFR для G1 и Parallel.

Пример графика длительности пейсинга для разных потоков:

Мы добавили линию allocation stalls (доступно только на ZGC) на график таймингов GC. Это позволяет переиспользовать один дашборд для разных сборщиков мусора. Паузы ZGC мы впоследствии побороли оптимизацией аллокаций в тяжёлых операциях, выполняющихся с заданной периодичностью.

Итоги эксперимента

ZGC может существенно снизить задержку, если вы готовы смириться с более высоким потреблением памяти (отказ от сжатых указателей, резерв под всплески аллокаций). Добавим сюда повышенное потребление CPU, отличающийся подход к диагностике и возможную несовместимость с низкоуровневыми библиотеками. 

История вторая: про FullGC limbo в Parallel

Итак, у нас были: 

  • Java 21

  • Большой хип (150 ГБ всего, 110 ГБ в oldgen) 

  • ParallelGC

  • Allocation rate 300 Мб/с 

  • Редкие FullGC сборки длительностью до 30 с

Сервис обслуживает запросы в формате Graphite и имеет низкую нагрузку циклического характера, хранит много данных в кеше. В какой‑то момент график GC поменял цвет — вместо минорных сборок каждый раз стала выполняться FullGC. При этом запросы на хост и соответственно нагрузка упали, свободного места в хипе было ещё свыше 30 ГБ, но 80% CPU уходили на циклические сборки мусора. 

В версиях Java до 23 у ParallelGC есть особенность. По умолчанию в p99 потребление CPU на GC не должно превышать 1%. Если из‑за сборки мусора пропускная способность приложения падает ниже параметра GCTimeRatio, используется эвристика «если ребёнок плачет, надо дать ему ремня» — если FullGC‑сборки слишком долгие, запускается ещё одна FullGC‑сборка, чтобы уменьшить размер молодого поколения (Parallel не умеет ресайзить без oldgen‑сборки). Это было видно по логам:

[2025-04-10T21:18:11.653+0300] GC(2187) Pause Full (Ergonomics)
[2025-04-10T21:18:30.897+0300] GC(2187) Marking Phase 19243.162ms
[2025-04-10T21:18:30.900+0300] GC(2187) Summary Phase 3.364ms
[2025-04-10T21:18:30.919+0300] GC(2187) Adjust Roots 19.211ms
[2025-04-10T21:18:35.609+0300] GC(2187) Compaction Phase 4689.771ms
[2025-04-10T21:18:36.659+0300] GC(2187) Post Compact 1050.232ms
[2025-04-10T21:18:36.660+0300] GC(2187) PSYoungGen: 19933184K(36180992K)->0K(36180992K) Eden: 19933184K(19933184K)->0K(19933184K) From: 0K(16247808K)->0K(16247808K)
[2025-04-10T21:18:36.660+0300] GC(2187) ParOldGen: 104171525K(104857600K)->104228125K(104857600K)
[2025-04-10T21:18:36.660+0300] GC(2187) Metaspace: 79632K(84736K)->79632K(84736K) NonClass: 70639K(73792K)->70639K(73792K) Class: 8992K(10944K)->8992K(10944K)
[2025-04-10T21:18:36.660+0300] GC(2187) Pause Full (Ergonomics) 121196M->101785M(137733M) 25006.171ms
[2025-04-10T21:18:36.660+0300] GC(2187) User=730.17s Sys=5.42s Real=25.00s
[2025-04-10T21:19:10.045+0300] GC(2188) Pause Full (Ergonomics)
[2025-04-10T21:19:28.744+0300] GC(2188) Marking Phase 18699.365ms
[2025-04-10T21:19:28.747+0300] GC(2188) Summary Phase 2.980ms
[2025-04-10T21:19:28.764+0300] GC(2188) Adjust Roots 16.858ms
[2025-04-10T21:19:34.807+0300] GC(2188) Compaction Phase 6043.200ms
[2025-04-10T21:19:35.821+0300] GC(2188) Post Compact 1013.695ms
[2025-04-10T21:19:35.821+0300] GC(2188) PSYoungGen: 19933184K(36180992K)->0K(36180992K) Eden: 19933184K(19933184K)->0K(19933184K) From: 0K(16247808K)->0K(16247808K)
[2025-04-10T21:19:35.821+0300] GC(2188) ParOldGen: 104228125K(104857600K)->104239415K(104857600K)
[2025-04-10T21:19:35.821+0300] GC(2188) Metaspace: 79632K(84736K)->79632K(84736K) NonClass: 70639K(73792K)->70639K(73792K) Class: 8992K(10944K)->8992K(10944K)
[2025-04-10T21:19:35.821+0300] GC(2188) Pause Full (Ergonomics) 121251M->101796M(137733M) 25776.528ms

Какие были варианты это исправить

  • отключить ресайз поколений через VM option ‑XX:‑UseAdaptiveSizePolicy

  • уменьшить таргет пропускной способности: ‑XX:GCTimePercentage=5;

  • перейти на Java 23, где эвристику удалили; 

  • выбрать другой сборщик мусора.

Наше решение 

Конкурентные сборки G1 потребляли значительно больше CPU, поэтому мы выбрали уже знакомый Generational ZGC.

Паузы пропали, удалось сэкономить CPU, даже всплески активности GC мало заметны на общей картине.

Итоги эксперимента

Пересматривать параметры GC после обновления JDK бывает полезно. Чистка мусора в Java‑приложениях с большими хипами больше не является проблемой.

История третья: про G1 и крупные объекты

Итак, у нас были: 

  • Java 23

  • G1 с таргетом 250 мс 

  • хип 30 ГБ

  • Allocation rate 50 Мб/с 

Сервис отвечает за создание метаданных для новых метрик, а также поиск метаданных метрик для читающих запросов. Обычно в системах мониторинга количество запросов на запись значительно превышает количество запросов на чтение, а рейт создания новых метрик также невысок, потому нагрузка на сервис невысокая. Аномалий в работе GC не было обнаружено, пока мы не разместили все GC‑метрики на одном дашборде. 

Мы видим, что реальные хиккапы в p99.99 превышают таргет и доходят до 1 секунды. Cборка мусора составляет половину от общего потребления CPU приложением, 20% от аллокаций попадает в oldgen: на старое поколение приходится 95% регионов. Визуализируем GC‑логи и видим, что значительное число регионов занимают Humongous‑объекты.

В логах видим, что эвристика G1 установила IHOP=0 Это значит, что часть регионов из старого поколения будет собираться конкурентно или в момент GC каждый раз, когда выполняется минорная сборка или аллоцируется крупный объект. 

GC(1920) Basic information (value update), threshold: 0B (0.00), target occupancy: 31406948352B, current occupancy: 16143684784B, recent allocation size: 186612376B, recent allocation duration: 52176.56ms, recent old gen allocation rate: 3576555.67B/s, recent marking phase length: 100534.64ms
GC(1920) Adaptive IHOP information (value update), threshold: 0B (0.00), internal target occupancy: 26695906099B, occupancy: 16143684784B, additional buffer size: 13421772800B, predicted old gen allocation rate: 338461145.70B/s, predicted marking phase length: 169470.15ms, prediction active: true
Про регионы G1

G1 использует фиксированный размер региона: обычно от 1 до 32 МБ, вычисляется эвристически в момент старта в зависимости от размера хипа. Можно задать размер региона вручную при помощи параметра ‑XX:G1HeapRegionSize (размер в мегабайтах должен быть равен степени двойки), тогда лимит на размер региона повышается до 512 МБ (начиная с Java 18).

Если объект занимает больше 50% от размера региона, он считается огромным (Humongous) и размещается не в обычных регионах, а в последовательности смежных регионов, выровненных по границе региона.
Такие объекты сразу попадают в oldgen и увеличивают promotion rate, но собираются в момент минорной сборки (если являются примитивными массивами; массивы объектов проверяются в минорной сборке начиная с Java 26).

Чтобы избежать FullGC, G1 при минорной сборке либо Humongous‑аллокации может запустить конкурентную сборку нескольких регионов старого поколения. Решение принимается путём сравнения текущей занятости хипа и значения InitialHeapOccupancyPercent (IHOP).
По умолчанию G1 адаптивно вычисляет IHOP как TargetOccupancy — (AllocationRate * MarkingDuration). В случае аллокации Humongous‑объектов IHOP снижается независимо от их времени жизни, и конкурентные циклы сборки старого поколения запускаются слишком часто и приносят мало пользы.

Какие были варианты это исправить

  • переписать приложение, раздробив крупные коллекции (потребует много времени на разработку); 

  • увеличить размер региона, используя -XX:G1HeapRegionSize

  • увеличить IHOP и отключить адаптивный режим:
    -XX:InitiatingHeapOccupancyPercent=80 -XX:-G1UseAdaptiveIHOP;

  • сменить сборщик мусора. 

Наше решение 

Для использования low‑latency GC было недостаточно ресурсов, потоки часто уходили в пейсинг, потому мы остались на G1. Мы увеличили размер региона до 256 МБ, после чего потребление CPU на сборку мусора снизилось в 25 раз, а хиккапы — в 5 раз в p99.9 и 15 в p99.99.

Это существенно отразилось на таймингах GRPC‑ответов, например, время ответа от ручки Find стабилизировалось на 55 мс в p99.99.

Итоги эксперимента

Включение дополнительных параметров в GC‑логах позволяют обнаружить аномалии в работе GC. Короткоживущие большие объекты ломают эвристики G1. Сокращение числа больших объектов (даже путём изменение понятия «большого объекта») может нивелировать проблему увеличения времени очистки отдельно взятого региона. 

Вывод

Мы приобрели экспертизу в области работы Java GC, настроили дашборды и алерты, позволяющие быстро обнаружить проблемы со сборкой мусора и понять причину изменений.

Нам не хватало метрик, поставляемых через GarbageCollectorMXBean, и мы стали собирать дополнительные метрики через JFR, дающие большую точность и улучшающие наблюдаемость за современными low‑latency коллекторами. Мы опубликовали библиотеку, в которой эти метрики собираются и экспортируются в формате OpenTelemetry, а также рабочий дашборд для Monium, которым мы постоянно пользуемся.

Рекомендации

  1. Используйте современные версии Java, они приносят действительно много впечатляющих возможностей: 

  2. Включите подробные логи GC. В наших сервисах мы используем следующие параметры: 

    -Xlog:async
    -Xlog:safepoint,gc*,gc+ergo=trace,gc+age=trace,gc+phases=trace,gc+humongous=trace,gc+ihop=trace:file=/logs/app-gc.log:tags,time,uptime,level:filecount=2,filesize=50M
    -Xlog:deoptimization=debug:file=/logs/app-deopt.log:time:filecount=2,filesize=50M
    

    Это позволяет следить за аномалиями в эвристике определения IHOP, смотреть размеры Humongous‑объектов и удобно визуализировать логи GC при помощи опенсорс‑инструментов. 

  3. Включите метрики GC и хиккапов. 

  4. Настройте GC‑дашборды. Обратите внимание на хиккапы, потребление CPU, размеры поколений, promotion rate.

  5. Встройте GC‑дашборд в релизную инструкцию и реагируйте на резкие изменения метрик. 

Спасибо. Делитесь своими историями тюнинга GC в комментариях и заглядывайте в чат пользователей Monium, где мы обсуждаем работу платформы, делимся новостями и фичами.