Вибір збирача сміття в Spring Boot для досягнення максимальної продуктивності з метою запобігання простоїв

pic

Ілюстрація пам'яті (створено за допомогою AI)

Уявіть собі фінансову компанію, яка стикається з серйозною проблемою: їхня онлайн-платіжна система раптово виходить з ладу через сплеск тисяч і навіть мільйонів процесів, що споживають пам'ять і процесорний час. Кількість скарг користувачів зростає, а команда розробників зайнята перевіркою сервісу, що не має балансування навантаження. Результат? Крім втрати часу і репутації, бізнес зазнає серйозних перешкод.

Ця проблема підкреслює важливість розуміння управління ресурсами та пам'яттю. Java використовує збирач сміття для автоматизованого керування пам'яттю, але не всі варіанти підходять для кожного процесу, і неправильний вибір може обтяжувати систему.

Отже, як можна забезпечити надійність сервісу з ефективним використанням пам'яті? Як вибрати правильний збирач сміття для потреб вашого додатку? Ця стаття відповість на ці питання, використовуючи практичний і зрозумілий підхід.

Java Virtual Machine (JVM)

Перед тим як обговорювати визначення збирача сміття, давайте спершу розберемося в JVM (Java Virtual Machine). Що таке Java Virtual Machine (JVM)? Програмне забезпечення JVM дозволяє виконувати код Java на різних пристроях і операційних системах, перетворюючи код Java на байт-код, інтерпретуючи його та виконуючи.

Уявіть собі Java Virtual Machine (JVM) як розумний копіювальний апарат, що обробляє код Java. Коли ви пишете код на Java, це як створювати оригінальний паперовий документ. Після компіляції код перетворюється на байт-код — особливу копію паперу, яку апарат може прочитати.

Одна з важливих функцій JVM — управління пам'яттю. JVM виконує роль менеджера пам'яті комп'ютера, регулюючи виділення та звільнення місця для даних програми. Уявіть собі JVM як тимчасовий лоток для зберігання на копіювальному апараті, але набагато складніший і ефективніший.

pic

Управління пам'яттю в JVM (джерело: Oracle)

У процесі управління пам'яттю JVM стикається з тією ж проблемою, що і в нашому повсякденному житті: обмежені ресурси. Щоб вирішити це, JVM оснащена автоматизованою системою під назвою збирач сміття. Ця система видаляє алокації пам'яті від об'єктів Java, які більше не використовуються.

Актор: Збирач сміття (GC)

Уявіть собі копіювальний апарат з обмеженим лотком для паперу. Збирач сміття — це як працівник, який прибирає непотрібні папери. Збирач сміття працює головним чином з пам'яттю на купі, яка є частиною пам'яті, де зберігаються всі виділення змінних і об'єктів у нашій програмі.

Чому саме пам'ять на купі? Її динамічна природа дозволяє нашій програмі працювати, а її пам'ять використовувати для подальших процесів. Щоб краще зрозуміти, давайте обговоримо структуру пам'яті купи.

pic

Структура пам'яті на купі (джерело: Oracle)

Для ефективного управління пам'яттю Java-купа розділяється на Young, Old та Permanent Generation. У цьому контексті події збору сміття поділяються на дві категорії: Minor GC і Major GC. Давайте уявимо, як працює цикл GC за допомогою аналогії.

Аналогія: Копіювальний магазин (зі збором сміття)

Уявіть собі копіювальний магазин, де документи (які представляють об'єкти) проходять через різні секції в залежності від того, як довго вони будуть використовуватися. Збір сміття гарантує, що непотрібні документи, як і видалення непотрібних об'єктів з пам'яті, не займають цінний простір.

Eden Space (Нові завдання, що чекають на копіювання)

  • Нові завдання: Усі нові документи (нові об'єкти) потрапляють сюди.
  • Minor Garbage Collection (Minor GC):
  1. Відбувається, коли кошик для “нових завдань” (Eden Space) заповнюється.
  2. Копіювальник (збирач сміття) швидко перевіряє документи:
  3. Невикористані документи (які більше не потрібні): Викидаються в сміття (збираються сміття).
    4.
    Важливі документи (ще в процесі використання): Переміщуються на тимчасову полицю (Survivor Space).

Вплив: Цей процес коротко припиняє роботу (Stop-the-World подія), але швидкий, оскільки очищається лише Молода Генерація.

Survivor Spaces (Архівовані для короткострокового використання)

  • Після того, як документи пережили перше очищення (Minor GC), вони переміщуються на тимчасову полицю (Survivor Space).
  • Документи можуть переміщатися між двома полицями (S0 і S1) під час регулярного очищення (Minor GC).

Якщо документ пережив кілька циклів очищення (досягнув порогу термінування), його вважають достатньо важливим для довготривалого зберігання і переміщують в Стару Генерацію.

Старша Генерація (Довготривале зберігання)

  • Довготривале зберігання: Документи, які пережили кілька раундів (Minor GC циклів), зберігаються в шафі (Стара Генерація).
  • Основне очищення сміття (Major GC):
  1. Відбувається, коли шафа (Стара Генерація) заповнюється.
  2. Копіювальник перевіряє всі шафи, викидаючи непотрібні документи (збираються сміття).

Вплив: Цей процес припиняє всі операції магазину (Stop-the-World подія) і займає більше часу, ніж Minor GC, тому він виконується рідше.

Постійна Генерація (Інструкція для копіювальника)

  • Цей розділ зберігає посібники (метадані про класи, методи та бібліотеки JVM), що допомагають магазину (JVM) функціонувати.
  • Коли магазин оновлюється до нового копіювальника (зміни в додатку) або більше не потребує певних посібників, застарілі видаляються (розвантаження класів під час повного GC).

Частина Повного збору сміття, що включає очищення Постійної Генерації, Старої Генерації та Молодої Генерації.

Тепер давайте перейдемо до суті і побачимо, як GC працює на практиці.

Налаштування системи моніторингу: Spring Boot, Prometheus, Grafana і Docker

Крок 1: Створення нового сервісу Spring Boot

Перш за все, потрібно створити новий сервіс Spring Boot. Ви можете згенерувати проект за допомогою Spring Initializr.

pic

Spring Boot Initializr

Не забудьте включити ці залежності; ви можете зробити це вручну або вибрати кнопку залежностей (у верхньому правому куті).

1. spring-boot-starter-undertow (необов'язково, легкий веб-сервер)  
2. micrometer-registry-prometheus (необхідно, дозволяє prometheus використовувати зібрані дані)  
3. spring-boot-starter-actuator (необхідно, має бути увімкнено для генерації метрик)  
4. spring-boot-starter-web (необхідно, оскільки додаток буде на основі rest api)

Структура вашого проекту повинна виглядати так.

pic

Структура проекту

Крок 2: Завантаження Prometheus та Grafana

Для налаштування моніторингу ми будемо використовувати Prometheus і Grafana. Замість того, щоб встановлювати окремі виконувані файли, ми використаємо Docker-образи для простішого оркестрування та управління. Ви можете пропустити цей крок і перейти до останнього.

docker pull prom/prometheus:v3.0.0  
docker pull grafana/grafana:11.0.8

Чому використовувати Docker?

Docker дозволяє нам відтворювати умови реального світу, особливо в умовах обмежених ресурсів (ЦП та пам'ять). Крім того, Docker спрощує моніторинг коливань ресурсів під час виконання додатку.

Крок 3: Налаштування Prometheus

Далі створіть файл конфігурації prometheus.yml у директорії проекту Spring Boot. Файл повинен виглядати так:

scrape_configs:  
 - job_name: 'spring-playground'  
 metrics_path: '/actuator/prometheus'  
 scrape_interval: 5s # Це можна налаштувати залежно від потреб  
 static_configs:  
 - targets: ['learning-core:8080'] # Це хост нашого сервісу

Крок 4: Створення Docker-образу для Spring Boot

Для зручності я використовую Dockerfile для створення Docker-образу додатку Spring Boot.
Нижче наведено скрипт:

FROM amazoncorretto:23.0.1-al2023-headful # образ JDK, ви можете використовувати інший, якщо бажаєте  

USER root  

COPY ./target/learning-core-0.0.1-SNAPSHOT.jar app.jar  

EXPOSE 8080 8081  

ENTRYPOINT ["java", "-jar", "/app.jar"]

Крок 5: Налаштування Docker Compose

Щоб інтегрувати три компоненти (Spring Boot, Prometheus і Grafana), створіть файл docker-compose.yml. Це забезпечить безперебійну роботу всіх сервісів разом. Ось повний скрипт:

version: "3.8"  
services:  
 learning-core:  
 build:  
 context: .  
 dockerfile: Dockerfile  
 container_name: learning-core  
 image: learning-core:0.0.1  
 environment:  
 - JAVA_TOOL_OPTIONS=-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:+UseContainerSupport -Xmx512m -Xms256m -XX:MaxMetaspaceSize=128m -XX:ReservedCodeCacheSize=64m -XX:MaxRAM=700m  
 deploy:  
 resources:  
 limits:  
 memory: 700m  
 cpus: "0.5" # Обмеження використання CPU до 500МБ (0.5 vCPU)  
 reservations:  
 memory: 512m  
 cpus: "0.2" # Резервування мінімальних ресурсів для базової роботи  
 ports:  
 - 8081:8080 # Сервіс додатка буде доступний через порт 8081  
 networks:  
 - monitoring  

 prometheus:  
 image: prom/prometheus:v3.0.0  
 container_name: prometheus  
 volumes:  
 - ./config/prometheus.yml:/etc/prometheus/prometheus.yml  
 - prometheus-data:/prometheus  
 ports:  
 - "9091:9090" # Сервіс Prometheus буде доступний через порт 9091  
 networks:  
 - monitoring  

 grafana:  
 image: grafana/grafana:11.0.8  
 container_name: grafana  
 volumes:  
 - grafana-data:/var/lib/grafana  
 ports:  
 - "3001:3000" # Сервіс Grafana буде доступний через порт 3001  
 depends_on:  
 - prometheus  
 networks:  
 - monitoring  

volumes:  
 grafana-data:  
 prometheus-data:  

networks:  
 monitoring:

Крок 6: Спрощення розгортання

Ви можете клонувати мій репозиторій тут, щоб запустити додаток без проблем налаштування. Не забудьте поставити зірочку! ⭐

[

GitHub - HellBus1/spring-playground

Сприяти розвитку HellBus1/spring-playground, створивши обліковий запис на GitHub.

github.com

](https://github.com/HellBus1/spring-playground.git?source=post_page-----3836a49a4e08--------------------------------)

Налаштування моніторингу

По-перше, переконайтеся, що ви можете відвідати сайт Prometheus і залишити його таким, як є (додаткові налаштування не потрібні). Він повинен виглядати ось так.

pic

Сайт Prometheus

По-друге, переконайтеся, що ви можете відвідати сайт Grafana. Стандартні ім’я користувача та пароль для входу: admin.

pic

Сайт Grafana

По-третє, для Prometheus як джерела даних визначте, чи має використовуватися URL сервера Prometheus як host.docker.internal або localhost, оскільки ми будемо використовувати внутрішню мережу Docker.

pic

Джерело даних Prometheus у Grafana

Нарешті, додайте панель моніторингу Grafana. Для спрощення я завантажив її з готової панелі моніторингу Grafana, замість того щоб створювати її самостійно через складний процес навчання. Я використовую Azure Cosmos: JVM & DB Metrics.

pic

Колекція панелей Grafana

Кінцевий вигляд повинен виглядати ось так (дані порожні, оскільки спостережуваний сервіс ще не працює), і ми готові до роботи.

pic

Вигляд панелі моніторингу

Спостереження через Grafana

Тестування буде проводитися за наступними критеріями:

  1. Сервіс буде залишений у стані простою на 10 хвилин перед викликом API /generate-pdf для генерації PDF.
  2. Час початку та закінчення процесу буде обчислено.
    3.
    Розглянемо, коли було викликано API та спостерігатимемо за використанням CPU (панель JVM Misc), пам’яті heap (панель JVM Memory) і активністю GC (панель Garbage Collector)

  3. Для кожного GC тест буде проведений двічі.

  4. Збирати та підсумовувати результати в таблиці.

1. Використання пам'яті heap

Чому це важливо: Високе використання пам'яті heap може призвести до частих збирань сміття, тривалих пауз GC та можливих помилок OutOfMemoryError.

Пороги:

  1. < 70%: Ідеально. JVM має достатньо пам'яті для роботи, що знижує частоту зборів сміття (GC).
  2. 70%–85%: Потрібно моніторити. Збільшене використання пам'яті heap може викликати частіші цикли GC.
  3. > 85%: Критично. Пам'ять heap наближається до виснаження, що призводить до частих або великих подій GC. Потрібно перевірити наявність витоків пам'яті або налаштувати розмір heap.

2. Використання CPU

Чому це важливо: Високе використання CPU під час GC може вказувати на неефективний розподіл пам'яті або необхідність більш ефективної стратегії GC (наприклад, G1GC, ZGC).

Пороги:

  1. < 50%: Ідеально. Вказує на те, що програма не є CPU-залежною.
  2. 50%–80%: Потрібно моніторити. Вказує на високий навантаження, але все ще кероване.
  3. > 80%: Проблемно. Ймовірно викличе затримки або проблеми з пропускною здатністю. Потрібно перевірити на наявність конфліктів потоків, зборів сміття або неефективних алгоритмів.

3. Паузи збору сміття

Чому це важливо: Продуктивність GC безпосередньо впливає на чутливість програми та пропускну здатність.

Пороги:

Можуть відрізнятися в залежності від типу Garbage Collector (див. аналіз кожного зборщика сміття).

Результати тесту Garbage Collector

Кожен результат тесту має три стани: Початковий стан, Під час виконання і Після обробки. Ми можемо моніторити пікове використання ресурсів процесу і зосередитись на стані, що виник під час виконання, до завершення обробки.
Оскільки нам потрібно перевірити найтриваліший час, ми повинні вимірювати лише під час пікових процесів для пауз GC.

Результати тесту G1 GC

Пам'ять heap

pic

Використання пам'яті heap G1 GC

Аналіз

  • Сталося о 07:29:30 AM
  • Почало сповільнюватися о 07:30:00 AM
  • Використана пам'ять heap: 183 МіБ
  • Забезпечена пам'ять heap: 256 МіБ
  • Макс. пам'ять heap: 512 МіБ

Обчислення

  • Відсоток використаної пам'яті від забезпеченої 183 / 256 * 100 = 71.48%
  • Відсоток використаної пам'яті від максимальної 183 / 512 * 100 = 35.74%
  • Тривалість від піку до сповільнення = ~30 секунд

Використана пам'ять heap 71.48% < 85% порівняно з забезпеченою пам'яттю (здорово)

CPU

pic

Використання CPU G1 GC

Аналіз

  • Сталося о 07:23:15 AM
  • Почало сповільнюватися о 07:24:00 AM

Обчислення

  • Система: Пік досягає 100% (сплеск).
  • Процес: Пік досягає 100% (сплеск).
  • Тривалість від піку до сповільнення: ~45 секунд

Використання CPU 100% > 80%, але це все ще прийнятно, оскільки сплеск був коротким (здорово)

Пауза Garbage Collector

pic

Пауза GC G1 GC

Поріг середньої паузи GC

  • Здорово: <100 мс
  • Прийнятно: 100–200 мс
  • Проблемно: >200 мс

Поріг максимальної паузи GC

  • Здорово: <200 мс
  • Прийнятно: 200–400 мс
  • Критично: >400 мс

Аналіз

  • Сталося о 07:23:15 AM
  • Середня пауза GC (Жовтий — мінімальна пауза G1 Evacuation): 86мс.
  • Максимальна пауза GC (Червоний — максимальна пауза G1 Evacuation): 199мс.

Середня пауза GC склала 86 мс < 100 мс (здорово)

Результати тесту Parallel GC

Пам'ять heap

pic

Використання пам'яті heap Parallel GC

Аналіз

  • Сталося о 00:46:30 AM
  • Почало сповільнюватися о 00:47:00 AM
  • Використана пам'ять heap: 97.6 МіБ
  • Забезпечена пам'ять heap: 293 МіБ
  • Макс. пам'ять heap: 512 МіБ

Обчислення

  • Відсоток використаної пам'яті від забезпеченої 97.6 / 293 * 100 = 33.3%
  • Відсоток використаної пам'яті від максимальної 97.6 / 512 * 100 = 19.06%
  • Тривалість від піку до сповільнення = ~30 секунд

Використана пам'ять heap 33.3% < 70% порівняно з забезпеченою пам'яттю (ідеально)

CPU

pic

Використання CPU Parallel GC

Аналіз

  • Сталося о 00:46:30 AM
  • Почало сповільнюватися о 00:47:00 AM

Обчислення

  • Система: Пік досягає 99.5%.
  • Процес: Пік досягає 99.6%.
  • Тривалість від піку до сповільнення: ~30 секунд

Використання CPU 99.6% > 80%, але це все ще прийнятно, оскільки сплеск був коротким (здорово)

Пауза Garbage Collector

pic

Пауза GC Parallel GC

Середня пауза GC

  • Здорово: <150 мс
  • Прийнятно: 150–300 мс
  • Проблемно: >300 мс

Максимальна пауза GC

  • Здорово: <300 мс
  • Прийнятно: 300–600 мс
  • Критично: >600 мс

Аналіз

  • Сталося о 00:46:45 AM
  • Середня пауза GC (Небесно-блакитний — Allocation Failure): 180 мс
  • Максимальна пауза GC (Океанічно-синій — Allocation Failure): 401 мс

Середня пауза GC склала 180 мс < 300 мс (здорово)

Результати тесту Serial GC

Пам'ять heap

pic

Використання пам'яті Serial GC

Аналіз

  • Сталося о 04:14:30 AM
  • Почало сповільнюватися о 04:15:00 AM
  • Використана пам'ять heap: 103 МіБ
  • Забезпечена пам'ять heap: 248 МіБ
  • Макс. пам'ять heap: 512 МіБ

Обчислення

  • Відсоток використаної пам'яті від забезпеченої 103 / 248 * 100 = 41.53%
  • Відсоток використаної пам'яті від максимальної 103 / 512 * 100 = 20.11%
  • Тривалість від піку до сповільнення = ~30 секунд

Використана пам'ять heap 41.53% < 70% порівняно з забезпеченою пам'яттю (ідеально)

CPU

pic

Використання CPU Serial GC

Аналіз

  • Сталося о 04:10:15 AM
  • Почало сповільнюватися о 04:11:15 AM

Обчислення

  • Система: Пік досягає 100%.
  • Процес: Пік досягає 100%.
  • Тривалість від піку до сповільнення: ~1 хвилина

Використання CPU 100% > 80%, але це все ще прийнятно, оскільки сплеск був коротким (здорово)

Пауза Garbage Collector

pic

Пауза GC Serial GC

Середня пауза GC

  • Здорово: <200 мс
  • Прийнятно: 200–500 мс
  • Проблемно: >500 мс

Максимальна пауза GC

  • Здорово: <500 мс
  • Прийнятно: 500 мс–1 секунда
  • Критично: >1 секунда

Аналіз

  • Сталося о 04:10:15 AM
  • Середня пауза GC (Небесно-блакитний — Allocation Failure): 158 мс
  • Максимальна пауза GC (Океанічно-синій — Allocation Failure): 308 мс

Середня пауза GC склала 158 мс < 200 мс (здорово)

Результати тесту Z GC

Пам'ять heap

pic

Використання пам'яті Z GC

Аналіз

  • Сталося о 04:13:00 AM
  • Почало сповільнюватися о 04:13:30 AM
  • Використана пам'ять heap: 105 МіБ
  • Забезпечена пам'ять heap: 248 МіБ
  • Макс. пам'ять heap: 512 МіБ

Обчислення

  • Відсоток використаної пам'яті від забезпеченої 105 / 293 * 100 = 35.83%
  • Відсоток використаної пам'яті від максимальної 105 / 512 * 100 = 20.51%
  • Тривалість від піку до сповільнення = ~30 секунд

Використана пам'ять heap 35.83% < 70% порівняно з забезпеченою пам'яттю (ідеально)

CPU

pic

Використання CPU Z GC

Аналіз

  • Сталося о 04:09:15 AM
  • Почало сповільнюватися о 04:09:45 AM

Обчислення

  • Система: Пік досягає 100%.
  • Процес: Пік досягає 100%.
  • Тривалість від піку до сповільнення: ~30 секунд

Використання CPU 100% > 80%, але це все ще прийнятно, оскільки сплеск був коротким (здорово)

Пауза Garbage Collector

pic

Пауза GC Z GC

Середня пауза GC

  • Здорово: <10 мс
  • Прийнятно: 10–50 мс
  • Проблемно: >50 мс

Максимальна пауза GC

  • Здорово: <50 мс
  • Прийнятно: 50–100 мс
  • Критично: >100 мс

Аналіз

  • Сталося о 04:09:15 AM
  • Середня пауза GC (Небесно-блакитний — Allocation Failure) складає 48.8 мс
  • Максимальна пауза GC (Океанічно-синій — Allocation Failure) складає 97 мс

Середня пауза GC склала 48.8 мс < 50 мс (здорово)

РЕЗЮМЕ

Об'єднані метрики для всіх Garbage Collectors

Огляд даних показує, що ZGC має найменші паузи — всього 48.8 мс, підтримуючи ефективне використання пам'яті heap на рівні 35.83%.
Це дає підказку, що підходить для сервісів, де важлива стабільність часу відгуку.

G1 GC показує вищу використану пам'ять heap на рівні 71.48% з помірними паузами 86 мс. Хоча ці показники можуть здатися не такими вражаючими на перший погляд, вони представляють збалансований підхід G1 до управління пам'яттю — ідеально підходить для багатьох корпоративних додатків.

Parallel GC демонструє свою ефективність з найменшим використанням пам'яті heap — 33.3%, хоча його паузи 180 мс можуть змусити деяких розробників замислитися. Тим часом Serial GC займає зручне середнє положення з 41.53% використаної пам'яті heap і паузами 158 мс.

Що далі?

Шлях до оптимізації збору сміття не є прямолінійним — він вимагає ретельного врахування ваших специфічних бізнес-вимог. Ось на що варто звернути увагу:

Зрозумійте основні вимоги вашого додатка

Почніть з того, щоб визначити ваші критичні сценарії:

  • Чи потрібно вам обробляти мільйони транзакцій в реальному часі, як у платіжних шлюзах?
  • Чи обробляєте ви великі пакети даних, що споживають великі обсяги пам'яті?
  • Чи має ваш додаток передбачувані шаблони використання пам'яті, що можуть виграти від використання об'єктних пулів?
  • Чи працюєте ви з тимчасовими об'єктами, що створюються і знищуються часто?

Враховуйте ваші операційні шаблони

Різні робочі навантаження вимагають різних підходів:

  • Обробка в реальному часі: Якщо ви обробляєте мільйони запитів на секунду, можливо, вам варто звернутися до ZGC для стабільної низької латентності.
  • Операції, що споживають багато пам'яті: Для додатків, що обробляють великі набори даних в пам'яті, G1 GC може бути найкращим вибором завдяки своїй ефективності управління пам'яттю.
  • Обробка пакетів: Коли справа доходить до обробки великих обсягів даних за розкладом, Parallel GC може забезпечити необхідну продуктивність.
  • Оточення з обмеженими ресурсами: Для простіших додатків або обмежених ресурсів, Serial GC може виявитися здивовано ефективним.

Пам'ятайте, що для збору сміття немає універсального рішення. Ключове — це розуміння ваших бізнес-вимог спочатку, а потім експериментування з різними збирачами сміття для знаходження найкращого підходу.

Які експерименти ви проводили з різними збирачами сміття? Поділіться своїм досвідом у коментарях!

Дякую за прочитання цієї статті. Не забудьте поставити лайк👏/рекомендувати, як тільки зможете, а також поділіться 📤 з друзями. Це дуже важливо для мене.

Також давайте станемо друзями на Linkedin, Twitter, або Instagram. Побачимось у наступній статті!

Перекладено з: Choosing a Garbage Collector in Spring Boot Effectively for Maximum Performance to Prevent Downtime

Leave a Reply

Your email address will not be published. Required fields are marked *