Java-служба: OutOfMemoryError и куча
У служб на JVM две границы памяти: размер кучи в настройках JVM и предел контрольной группы в unit-файле. Нехватка первой даёт ошибку внутри приложения, превышение второй — убийство процесса ядром.
Вероятные причины
По порядку: сверху то, что встречается чаще.
-
Куче не хватает памяти
Приложение выбрасывает ошибку нехватки памяти и может продолжать работать в неработоспособном состоянии. Это хуже падения: служба «активна», а запросы не обрабатываются.
-
Предел контрольной группы ниже потребности JVM
JVM расходует память и вне кучи: метаданные, стеки потоков, буферы. Предел должен быть больше кучи с запасом.
-
Дамп кучи не сохраняется, и причина неизвестна
Без дампа непонятно, что заняло память. Разбор превращается в догадки.
Диагностика
Команды идут в том порядке, в котором их стоит выполнять: каждая следующая проверяет то, что осталось после предыдущей.
Ошибки нехватки памяти в журнале.
journalctl -u myapp.service -n 60 --no-pager | grep -iE "OutOfMemory|GC overhead"Предел и пиковое потребление службы.
systemctl show myapp.service -p MemoryMax -p MemoryPeakУбивало ли процесс ядро.
journalctl -k -b --no-pager | grep -i "killed process" | grep -i javaРешение
Решение своё для каждой причины. Сначала определите, какая из них ваша, — иначе правки наложатся друг на друга.
- Почему происходит
- Приложение выбрасывает ошибку нехватки памяти и может продолжать работать в неработоспособном состоянии. Это хуже падения: служба «активна», а запросы не обрабатываются.
- Как проверить
-
Посмотрите журнал и размер кучи.
journalctl -u myapp.service -n 40 --no-pager | grep -i "OutOfMemoryError" systemctl cat myapp.service | grep -iE "Xmx|Xms"
- Как исправить
-
Увеличьте кучу или уменьшите потребление. Добавьте ключ, завершающий процесс при нехватке памяти: тогда systemd перезапустит службу вместо работы в сломанном виде.
# в ExecStart: -XX:+ExitOnOutOfMemoryError
- Почему происходит
- JVM расходует память и вне кучи: метаданные, стеки потоков, буферы. Предел должен быть больше кучи с запасом.
- Как проверить
-
Сравните предел и размер кучи.
systemctl show myapp.service -p MemoryMax -p MemoryPeak systemctl cat myapp.service | grep -i Xmx
- Как исправить
- Задайте предел службы с запасом примерно в треть от размера кучи, либо укажите JVM долю памяти контейнера вместо абсолютного размера.
- Почему происходит
- Без дампа непонятно, что заняло память. Разбор превращается в догадки.
- Как проверить
-
Посмотрите, включено ли сохранение дампа.
systemctl cat myapp.service | grep -i HeapDump
- Как исправить
-
Включите сохранение дампа кучи при нехватке памяти в каталог службы: файл пригодится для разбора.
# -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/lib/myapp
Пример вывода
Куче не хватило памяти, процесс остался в работе. Пример показательный: он собран на тестовой машине специально для этой страницы, а не взят из чужого журнала.
myapp[1200]: Exception in thread "http-worker-12" java.lang.OutOfMemoryError: Java heap space
myapp[1200]: java.lang.OutOfMemoryError: GC overhead limit exceeded
# служба при этом остаётся active (running)
Связанные ошибки
- Failed with result 'oom-kill' Состояние oom-kill: процесс службы убит из-за нехватки памяти. Как понять, упёрлись в предел службы или в память машины.
- Cannot allocate memory в журнале службы Не удалось выделить память: предел службы, память машины, настройка overcommit, исчерпанные области отображения.
- Elasticsearch: служба падает из-за размера кучи JVM Размер кучи больше доступной памяти или больше предела службы: падение при старте или под нагрузкой.
- Hardware Error / EDAC: ошибки памяти в журнале Ядро сообщает об ошибках памяти. Что считать безобидным, а что поводом менять модуль.
- Java-служба не запускается: не найдена среда исполнения Служба на JVM падает с кодом 203 или 127: путь к java неверен или среда исполнения не установлена.
- MySQL убит из-за памяти: буферный пул больше доступной памяти MySQL падает сразу после старта или через минуты: размер innodb_buffer_pool_size превышает память машины.
- RabbitMQ: тревога по свободному месту блокирует отправителей Брокер перестаёт принимать сообщения: сработала тревога по диску или памяти. Как снять и что настроить.
- Redis: OOM command not allowed when used memory exceeds maxmemory Redis отклоняет запись: достигнут maxmemory, а политика вытеснения не позволяет освобождать память.
Где встречается чаще всего
Источники
- Документация JVM: параметры памяти
-
Воспроизведено на тестовой машине, systemd 255 (Ubuntu 24.04)
Воспроизведено приложением на JVM с кучей больше предела контрольной группы.