Как анализировать OOM ошибки?

| April 30, 2023

В этой статье речь пойдёт о памяти JVM и проблемах, связанных с ней. Перед прочтением рекомендуется освежить знания о базовых вещах. На сайте как раз доступна статья о “Модели памяти Java”.

В век микросервисов и контейнеров, довольная стандартная история использовать Kubernetes в качестве платформы для оркестрации контейнеров. И так же, довольно распространена история, когда контейнер, с Вашим любимым сервисом, Kubernetes начинает периодически “убивать”. Возможно, Ваш привычный алёртинг истошно верещит о недоступности pod’а. И Вы, используя привычную команду:

kubectl describe pod [name]

видите нечто, похожее на следующее:

State:          Running
   Started:      Thu, 25 Apr 2023 09:11:47 +0400
   Last State:   Terminated
   Reason:       OOMKilled
   Exit Code:    137
   ...

“И что теперь делать?” - думаете Вы…

Если история выше это про Вас, тогда, в этой статье, Вы найдёте целый каталог с видами ошибок и возможными вариантами их обойти. Усаживайтесь поудобнее, мы начинаем 🚀.

Reason: OOMKilled

Reason:       OOMKilled

OOMKilled в графе о причинах отказа pod’а означает, что встроенный OOM Killer виновен в останове работы pod’а, а так же, является сигналом к тому, чтобы пойти и посмотреть логи Вашего приложения. С большой долей вероятности, в логах, Вы найдёте java.lang.OutOfMemoryError.

java.lang.OutOfMemoryError

java.lang.OutOfMemoryError является одним из признаков возможной утечки памяти. Зачастую, эта ошибка происходит тогда, когда сборщик мусора не смог освободить память для создания очередного объекта в heap’е и heap не может быть расширен более. Она так же, может происходить тогда, как в JVM закончилась нативная память и, в ряде других случаев.

java.lang.OutOfMemoryError логгируется вместе со стектрейсом и имеет специфичное сообщение об ошибке. Именно сообщение об ошибке является подсказкой о том, в какую сторону “копать”.

Далее, пробежимся по основным сообщениям этого исключения и поговорим о том, что нужно делать в той или иной ситуации.

Java heap space

Причина

Закончилось место в heap’е JVM: она попыталась создать новый объект в eden space, у неё ничего не вышло, и это привело к завершению её работы с этой ошибкой.

Ошибка НЕ означает, что в Вашей программе есть утечка памяти. В конце концов, это может быть просто ошибка конфигурации, и изначальные настройки содержат указание уровня памяти, которого недостаточно для нормальной работы приложения.

Что делать

Ошибки конфигурации могут быть решены довольно просто, увеличением лимитов памяти heap’а, с помощью следующих флагов JVM:

-Xms (начальный размер памяти JVM)
-Xmx (максимальный размер памяти JVM)

Если Вы столкнулись с ошибкой конфигурации памяти - считайте Вам повезло. Совершенно другая история, если лимиты неоднократно увеличивались, а проблема не уходит. В таком случае, Вы столкнулись с утечкой памяти (memory leak), и в этом случае, требуется тонкий анализ того, как именно создаются объекты в Вашей программе, и почему память, занимаемая ими не освобождается. Например, можно поискать коллекции, заполняемые массой объектов, и, ссылки на которые, есть в каких-нибудь синглтонах (👋 привет, Spring Boot).

GC Overhead limit exceeded

Причина

Последние 5 сессий сборки мусора, прошедшие подряд, ~98% времени работы приложения сборщик мусора тратил на сборку мусора и, при этом освобождал не более 2% памяти heap’а. Иными словами, в heap’е достаточно места для работы программы, но практически не остаётся запаса для его расширения.

Что делать

Использовать -Xmx параметр JVM для увеличения размера heap’а.

Requested array size exceeds VM limit

Причина

Произошла попытка проинициализировать массив размером больше, чем JVM может создать.

Чтобы получить на практике этот вид OutOfMemoryError ошибки, достаточно попробовать выполнить код со следующей конструкцией:

var array = new Integer[Integer.MAX_VALUE];

Что делать

Следует просмотреть код на предмет инициализации массивов. В большинстве случаев, обнаружится либо динамическое вычисление размерности массива, либо его бесконечное расширение (опять же, через расчёт новой размерности). Это поможет вычислить подозреваемого.

Metaspace

Причина

Свободная память в Metaspace пространстве закончилась.

Что делать

Metaspace расположена в том же адресном пространстве, что и heap. Поэтому JVM может распределять память между разными пространствами (metaspace, heap и т.д.)

  • либо, автоматически, следуя простой логике:
    ПространствоМаксимальный размер
    Java Heap75%
    Metaspace10%
    Другое15%
  • либо, на основе заданных лимитов

В первом случае, достаточно увеличить максимальный размер памяти, выдаваемый JVM, во втором случае, можно воспользоваться параметром -XX:MaxMetaspaceSize для установки конкретного значения максимального объёма Metaspace.

request [size] bytes for [reason]. Out of swap space?

Причина

Происходит тогда, когда JVM не смогла выделить память на очередной запрос и память в heap’е почти закончилось.

[size] в сообщении об ошибке содержит указание запрошенного объёма памяти, а [reason] - название модуля, от которого пришёл запрос.

Что делать

Обратить внимание на Fatal Error Log. Он пишется как раз тогда, когда происходит эта ошибка и содержит необходимую диагностическую информацию для анализа проблемы.

Compressed class space

Причина

Включена оптимизация, согласно которой указатели на метаданные классов будут сжаты. Если конкретно - на 64-битных JVM указатели на метаданные классов могут быть как 64-х битные, так и 32-х, если включен флаг -XX:UseCompressedClassPointers (включено по умолчанию). Если оптимизация включена, то размер доступной памяти для указателей регулируется параметром -XX:CompressedClassSpaceSize. И наконец, ошибка происходит тогда, когда размер этой области памяти превышает лимит заданный вышеуказанным параметром.

Что делать

Увеличить размер -XX:CompressedClassSpaceSize

reason stack_trace (Native method)

Причина

JVM не смогла выделить память, во время выполнения метода, отмеченного ключевым словом native.

Что делать

Причины могут быть разные. Для детального анализа проблемы, рекомендую ознакомиться с инструментами, которые помогут проанализировать проблему - Native Operating System Tools.

Заключительная часть

OutOfMemoryError ошибки могут быть довольно устрашающими, если не знать, как с ними работать. Надеюсь эта статья, поможет разобраться с Вашей проблемой. Больше материалов о памяти в JVM скоро на ⭐️ Boosty.

Список материалов

  • 🇬🇧 Troubleshooting Memory Leaks - наиважнейший документ в плане источника информации об утечках памяти и их поиске