Алгоритм расчета память для java-сервисов
Вводная часть
Данный раздел посвящен объяснению работы и возможностью переиспользования утилиты по динамическому расчёту памяти для systemd сервисов.
Теоретическая часть
Общая формула:
Количество доступной RAM = Общее RAM - Резерв операционной системы - Безопасный буфер
Лимит сервисов = (Количество доступной RAM * Приоритет) / Сумму всех приоритетов
Бюджет JVM = Лимит сервиса - NonHeap резерв
Xmx = (Бюджет JVM * HeapRatio) -> округление -> ограничения
MemoryMax = (Xmx / MaxRAMPercentage) -> округляем чтобы была кратна 2-м(128)
Определение доступной памяти
Общий RAM системы - общий RAM КБ / 1024
Автоматический расчёт резервов - чем больше памяти в системе, тем меньше процент резерва (эффект масштаба)
| RAM системы | Резерв ОС | Безопасный буфер | Общий резерв |
|---|---|---|---|
| =< 2 ГБ | 20% | 10% | 30% |
| =< 8 ГБ | 15% | 8% | 23% |
| =< 32 ГБ | 10% | 5% | 15% |
| > 32 ГБ | 8% | 4% | 12% |
RAM_available = RAM_total - OS_reserve - safety_buffer
Распределение по приоритетам
Приоритет - это относительная доля, а не абсолютное значение. Увеличение приоритета одного сервиса уменьшает долю остальных
Конфигурация сервисов в формате - service_name:priority:java_version, где 1 это низкий, а 5 это высокий
Формула распределения MemoryLimit_service = (RAM_available * priority_service) / All_weights
Пример: RAM_available = 10000 MB и сервисы: pam-wdog(5) + pam-gui(10) + pam-auth(1) = 16
| Сервис | Приоритет | Расчёт | Лимит |
|---|---|---|---|
| pam-gui | 10 | 10000 * 10 / 16 | 6250 MB |
| pam-wdog | 5 | 10000 * 5 / 16 | 3125 MB |
| pam-auth | 1 | 10000 * 1 / 16 | 625 MB |
Расчёт Non-Heap резерва
JVM использует память не только для кучи (Heap). Скрипт автоматически резервирует место под:
- Metaspace (метаданные классов)
- CodeCache (JIT-компиляция)
- Потоки (stack memory)
- Direct buffers (NIO)
- GC overhead
Базовая формула: NonHeap = 400 MB + JavaAdjustment + PriorityAdjustment
Корректировки:
| Условие | Корректировка | Пояснение |
|---|---|---|
| По умолчанию | +400 MB | Стандартный резерв |
| Java 8 | +50 MB | Требует больше резерва |
| Java 21+ | -50 MB | Более эффективное управление памятью |
| Приоритет ≥ 5 | +100 MB | Больше запаса для критичных сервисов |
| Приоритет =< 2 | -50 MB | Меньше запаса для лёгких сервисов |
Пример:
# pam-wdog: priority=5, Java=8
NonHeap = 400 + 50 (Java8) + 100 (high-priority) = 550 MB
# pam-auth: priority=1, Java=21
NonHeap = 400 - 50 (Java21) - 50 (low-priority) = 300 MB
Расчёт Heap (Xmx)
Формула JVM бюджета: JVM_budget = MemoryLimit_service - NonHeap
Heap Ratio по приоритету. Определяет, какую часть JVM-бюджета отдать под Heap, а сколько оставить на “надстройку”
| Приоритет | Heap Ratio | Пояснение |
|---|---|---|
| ≥ 5 (критичный) | 0.70 | Больше запаса на Non-Heap для стабильности |
| 3–4 (средний) | 0.75 | Баланс |
| =< 2 (лёгкий) | 0.80 | Максимум памяти под данные, минимум запаса |
Финальный расчёт Xmx. Округление до 64 МБ для совместимости с аллокаторами памяти. Максимум 90% от лимита для защиты от выхода за границы cgroup
Xmx_raw = JVM_budget * HeapRatio
Xmx = round_up_to_64MB(Xmx_raw)
Xmx = clamp(Xmx, min=256, max=MemoryLimit * 0.90)
Пример полного расчёта:
Сервис: pam-wdog, priority=5, Java=8
Система: RAM_available = 10000 MB, All_weights = 16
1. MemoryLimit = 10000 * 5 / 16 = 3125 MB
2. NonHeap = 400 + 50 + 100 = 550 MB
3. JVM_budget = 3125 - 550 = 2575 MB
4. HeapRatio = 0.70 (priority ≥ 5)
5. Xmx_raw = 2575 * 0.70 = 1802.5 MB
6. Xmx = round_up_64(1802.5) = 1856 MB
7. Проверка: 1856 < (3125 * 0.90 = 2812)
Итог: -Xms1856m -Xmx1856m
Использование -Xms (начальный размер кучи) и -Xmx (максимальный размер кучи) с одним и тем же значением рекомендуется для высоконагруженных Java-приложений
Это обеспечивает предсказуемость, устраняет паузы при изменении размера кучи JVM и повышает производительность за счет выделения памяти заранее
Расчёт MemoryMax для systemd
Чтобы systemd не убил процесс раньше времени, устанавливаем лимит cgroup
MemoryMax = Xmx / MaxRAMPercentage
MemoryMax = round_up_to_128MB(MemoryMax)
При MaxRAMPercentage=75.0:
MemoryMax = Xmx / 0.75 = Xmx * 1.333...
Пример:
Xmx = 1856 MB
MemoryMax = 1856 / 0.75 = 2474.67 -> round_up_128 = 2560 MB
systemd: MemoryMax=2560M
Сводная таблица параметров
| Параметр | Формула | Значение |
|---|---|---|
RAM_available | RAM_total - OS_reserve - safety | 10000 MB |
MemoryLimit | (RAM_avail * priority) / All_weights | 3125 MB |
NonHeap | 400 + java_adj + priority_adj | 550 MB |
JVM_budget | MemoryLimit - NonHeap | 2575 MB |
HeapRatio | 0.70 / 0.75 / 0.80 | 0.70 |
Xmx | clamp(round64(JVM_budget * ratio), 256, 90%) | 1856 MB |
MemoryMax | round128(Xmx / 0.75) | 2560 MB |
Визуализация распределения памяти
Система: 16 ГБ (16384 MB)
├─ Резерв ОС (15%) : 2458 MB
├─ Буфер безопасности (8%): 1311 MB
└─ Доступно сервисам : 12615 MB
│
├─ pam-gui (pri=10, 62.5%)
│ ├─ Лимит : 7884 MB
│ ├─ NonHeap : 550 MB
│ ├─ Heap (70%) : ~5136 MB -> -Xmx5120m
│ └─ MemoryMax : 6912M
│
├─ pam-wdog (pri=5, 31.25%)
│ ├─ Лимит : 3942 MB
│ ├─ NonHeap : 550 MB
│ ├─ Heap (70%) : ~2374 MB -> -Xmx2400m
│ └─ MemoryMax : 3200M
│
└─ pam-auth (pri=1, 6.25%)
├─ Лимит : 788 MB
├─ NonHeap : 300 MB
├─ Heap (80%) : ~390 MB -> -Xmx384m
└─ MemoryMax : 512M
Рекомендации по настройке приоритетов
Сумма приоритетов не ограничена, но разумный диапазон - 1..10. Слишком большой разрыв (1-100) лишает лёгкие сервисы памяти
| Приоритет | Назначение | Примеры |
|---|---|---|
| 1–2 (низкий) | Фоновые задачи, health-check, утилиты | health-check, log-shipper |
| 3–4 (средний) | Обычные микросервисы, не критичные к просадкам | notification, reporting |
| 5–7 (высокий) | Критичные сервисы, API | auth, order-service |
| 8–10 (критичный) | Ядро системы, шлюзы | payment, api-gateway |
Практическая часть
В данном блоке будут рассмотрены частные случаи использования утилиты как отдельного компонента для динамического перерасчёта памяти для сервисов
Пример получения справки
→ sudo ./auto-java-memory.sh --help
auto-java-memory.sh - automatic memory calculation for Java services
Usage: auto-java-memory.sh --config <services.conf> --unit-dir <path> [--dry-run] [--verbose]
Conf file format parse style: service_name:priority:java_version
Example: pam-gui:5:8 pam-auth:3:17 pam-ssh:1:21
Parameters:
--config - path to the file containing the list of services
--unit-dir - directory with systemd unit files (default: /etc/systemd/system)
--dry-run - show calculations without application
--verbose - verbose output
--help - show help
Usage example: auto-java-memory.sh --config /etc/app/services.conf --unit-dir /etc/systemd/system --dry-run
Version: v1.0.0
Пример использования в инсталляторе
sudo bash "./tools/auto-java-memory.sh" \
--config "./tools/upgrade-services-unit.conf" \
--unit-dir /usr/lib/systemd/system
systemctl daemon-reload
SERVICES=(
"pam-gui.service" "log.io-harvester.service" "log.io-server.service"
"pam-ssh.service" "pam-sftp.service" "pam-sql.service"
"pam-http.service" "pam-rdpgw.service" "pam-radius.service"
"pam-wdog.service" "pam-diag.timer" "pam-diag.service"
"pam-auth.service" "pam-tacacs.service"
)
for i in "${SERVICES[@]}"; do
log_line "info" "Activating '${i}' with the latest configuration..."
systemctl enable --now "${i}"
sleep 5
systemctl -q is-active "${i}" || log_line "warning" "service '${i}' is not active at now"
done
Пример использования при перерасчёте памяти
# Запуская проверку, чтобы убудиться о том, какие параметры в соотвествии с весами будут применены
sudo ./auto-java-memory.sh \
--config ./upgrade-services-unit.conf \
--unit-dir /usr/lib/systemd/system \
--dry-run
# Запуск "боевого" режима
sudo ./auto-java-memory.sh \
--config ./upgrade-services-unit.conf \
--unit-dir /usr/lib/systemd/system
systemctl daemon-reload
iuda -r all
Дополнительные хистрости
Данный раздел будет посвящен больше не утилите, а о работе с systemd сервисами. Бывают ситуации, когда 1 сервис начинает потреблять большое количество CPU/IO ресурсов, что, конечно, не приятно. Об этом нужно сообщать разработчикам, чтобы вышел соотвествующий патч, но что делать ДО этого момента? Systemd умеет работать напрямую с Cgroupsv2 - механизм ограничения сервисов, при котором напрямую сообщается о том скролько ресурсов выделять ему в данный момент. Пример содержимого:
Пример ограничения юнита systemd
[Service]
MemoryMax=256M
MemorySwapMax=0
CPUQuota=50%
IOReadBandwidthMax=/dev/sda 10M
IOWriteBandwidthMax=/dev/sda 5M
TasksMax=100
Цикл статей по этой тематике с практическим применением “временных заплаток до выхода патча”:
Как ограничить ресурсы процессов в Linux с помощью cgroups и systemd
Зачем администратору разбираться в cgroups v2, даже если «есть контейнеры»