🧠 Раздел 3 · Вопрос #20

Какие типы OutOfMemoryError существуют

Класс java.lang.OutOfMemoryError (OOME) является подклассом VirtualMachineError и сигнализирует о невозможности виртуальной машины выделить необходимый объем памяти. Конкретная...


🟢 Junior Level

Класс java.lang.OutOfMemoryError (OOME) является подклассом VirtualMachineError и сигнализирует о невозможности виртуальной машины выделить необходимый объем памяти. Конкретная причина ошибки всегда указывается в строке сообщения (detail message).

Основные типы OOME, встречающиеся на практике:

Тип сообщения OOME Область памяти / Причина Что означает простыми словами
Java heap space Java Heap Закончилась куча для обычных Java-объектов.
GC overhead limit exceeded CPU / Garbage Collector Сборщик мусора тратит всё процессорное время, но почти ничего не освобождает.
Metaspace Native Memory (Метаданные классов) Загружено слишком много классов или произошла утечка ClassLoader.
Direct buffer memory Off-Heap (NIO буферы) Исчерпан лимит прямой памяти, выделяемой через ByteBuffer.allocateDirect().
unable to create new native thread Потоки ОС / Стек ОС не позволяет процессу создать новый поток (закончилась память под стеки или превышен системный лимит).
Requested array size exceeds VM limit Java Heap (Массивы) Попытка создать массив с размером больше допустимого лимита JVM.

🟡 Middle Level

1. java.lang.OutOfMemoryError: Java heap space

  • Механика: Поток запрашивает память под новый объект или массив в куче, но доступного непрерывного пространства нет даже после вызова Full GC и очистки ссылок SoftReference.
  • Основные причины:
    • Реальная утечка памяти (Memory Leak): объекты больше не нужны, но на них остаются сильные ссылки из статических полей, долгоживущих кэшей или коллекций.
    • Недостаточный максимальный размер кучи: параметры -Xmx или -XX:MaxRAMPercentage заданы слишком малыми для пиковой бизнес-нагрузки.
  • Диагностика и устранение: Снять Heap Dump (-XX:+HeapDumpOnOutOfMemoryError) и проанализировать граф доминаторов в Eclipse Memory Analyzer (MAT) или IDEA Profiler.

2. java.lang.OutOfMemoryError: GC overhead limit exceeded

  • Механика: Защитная эвристика HotSpot JVM. Если сборщик мусора тратит более 98% времени процессора на выполнение пауз GC и при этом освобождает менее 2% памяти кучи на протяжении 5 последовательных циклов, JVM выбрасывает эту ошибку.
  • Цель эвристики: Предотвратить состояние thrashing (паралич приложения), когда 100% CPU уходит на бесполезный сбор мусора, а полезная работа не выполняется.
  • Частая ошибка: Отключение эвристики флагом -XX:-UseGCOverheadLimit. Это антипаттерн: приложение просто зависнет в 100% CPU на десятки минут и в итоге всё равно упадёт с Java heap space.

3. java.lang.OutOfMemoryError: Metaspace

  • Механика: Память Metaspace располагается в Native Memory процесса (вне кучи) и хранит структуры метаданных классов (Klass, таблицы виртуальных методов vtable, константные пулы). Ошибка возникает при превышении флага -XX:MaxMetaspaceSize или исчерпании физической RAM хоста.
  • Причины:
    • Динамическая кодогенерация (CGLIB, ByteBuddy, Spring AOP, Hibernate, Groovy/JRuby скрипты), создающая бесконечное количество новых классов.
    • Утечка загрузчиков классов (ClassLoader Leak): старый ClassLoader не может быть собран GC, удерживая тысячи метаданных загруженных классов.
  • Диагностика: jcmd <pid> VM.metaspace и анализ ClassLoader-ов в профилировщике.

4. java.lang.OutOfMemoryError: Direct buffer memory

  • Механика: Выделение памяти через ByteBuffer.allocateDirect() (NIO) обращается к нативному вызову malloc(). HotSpot отслеживает суммарный объем выделенной прямой памяти через класс jdk.internal.misc.VM / Bits. Если суммарный объем превышает лимит -XX:MaxDirectMemorySize (по умолчанию равен размеру -Xmx), бросается данная ошибка.
  • Причины: Утечки буферов в сетевых библиотеках (Netty, gRPC, Kafka client), когда буферы не освобождаются вызовом ReferenceCountUtil.release() или не успевают очищаться через Cleaner / PhantomReference.
  • Особенность: Память в Java Heap при этом может быть заполнена всего на 5–10%!

5. java.lang.OutOfMemoryError: unable to create new native thread

  • Механика: Конструктор new Thread().start() обращается к вызову ядра ОС (clone() или pthread_create() в Linux). Если ядро ОС возвращает код ошибки EAGAIN или ENOMEM, JVM транслирует его в OOME.
  • Причины:
    1. Достигнут лимит потоков процесса или пользователя в ОС (ulimit -u или /proc/sys/kernel/threads-max).
    2. Достигнут системный лимит виртуальных областей памяти vm.max_map_count (каждый стек потока требует 2 области mmap — стек и guard page).
    3. Недостаточно свободной оперативной памяти под стеки потоков (-Xss, по умолчанию 1 МБ на поток).
  • Парадокс: Если увеличить -Xmx, эта ошибка проявится быстрее, так как куча займет больше памяти, оставив меньше нативной памяти под стеки потоков.

🔴 Senior Level

1. Редкие и низкоуровневые типы OOME

+-------------------------------------------------------------------------------+
|                      Спектр ошибок OutOfMemoryError                           |
+-------------------------------------------------------------------------------+
| [Java Heap]                                                                   |
|   ├── Java heap space (недостаток места в Eden/Tenured)                       |
|   ├── GC overhead limit exceeded (98% CPU на GC, <2% очищено)                 |
|   └── Requested array size exceeds VM limit (массив > Integer.MAX_VALUE - 8)  |
+-------------------------------------------------------------------------------+
| [Metaspace / Class Metadata]                                                  |
|   ├── Metaspace (превышен -XX:MaxMetaspaceSize)                               |
|   └── Compressed class space (превышен -XX:CompressedClassSpaceSize)          |
+-------------------------------------------------------------------------------+
| [Native / OS Memory]                                                          |
|   ├── Direct buffer memory (превышен -XX:MaxDirectMemorySize)                 |
|   ├── unable to create new native thread (лимиты ulimit / pthreads / RAM)     |
|   ├── request <size> bytes for <reason>. Out of swap space? (malloc failed)   |
|   └── Map failed (исчерпан sysctl vm.max_map_count при MappedByteBuffer)      |
+-------------------------------------------------------------------------------+

А. Compressed class space

При включенном сжатии ссылок на классы (-XX:+UseCompressedClassPointers, активно по умолчанию в 64-бит JVM) все структуры метаданных Klass размещаются в едином непрерывном 32-битном адресном пространстве размером 1 ГБ (по умолчанию, настраивается через -XX:CompressedClassSpaceSize=1g).

  • Даже если общий объем Metaspace свободен и имеет запас, переполнение 1 ГБ пространства сжатых классов приведёт к: java.lang.OutOfMemoryError: Compressed class space.

Б. Requested array size exceeds VM limit

Возникает при попытке создания массива с длиной, превышающей технический максимум JVM:

// Вызовет: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
int[] hugeArray = new int[Integer.MAX_VALUE]; 

Заголовок массива в HotSpot требует место под служебные поля (длина массива length занимает 4 байта в заголовке объекта). Поэтому предельный размер массива составляет от Integer.MAX_VALUE - 2 до Integer.MAX_VALUE - 8 в зависимости от версии платформы.

В. request <size> bytes for <reason>. Out of swap space?

Низкоуровневый сбой аллокатора C++ (os::malloc вернул NULL). Свидетельствует о полном физическом исчерпании RAM и Swap-пространства хоста.

  • Сопровождается генерацией аварийного лога hs_err_pid.log.
  • Причины: утечка памяти в сторонней C/C++ библиотеке (через JNI), утечка Code Cache (компилятор JIT), или служебных структур данных сборщика мусора.

2. Сравнительная матрица локализации и устранения

Сообщение об ошибке Первичный шаг диагностики Ключевые флаги конфигурации
Java heap space Анализ Heap Dump в MAT / JProfiler -Xmx, -XX:MaxRAMPercentage
GC overhead limit Снятие GC-логов (-Xlog:gc*), поиск утечки Оптимизация аллокаций, рефакторинг
Metaspace jcmd <pid> VM.metaspace show-loaders -XX:MaxMetaspaceSize
Compressed class space Проверка генерации прокси-классов -XX:CompressedClassSpaceSize
Direct buffer memory Поиск утечек в Netty / NIO, NMT -XX:MaxDirectMemorySize, -Dio.netty.leakDetectionLevel
unable to create thread Проверка ulimit -u, подсчёт потоков jstack Пул потоков, Virtual Threads (Java 21+), -Xss
Out of swap space Проверка памяти ОС (free -m, vmstat), NMT Исправление Native/JNI утечек, добавление RAM

🎯 Шпаргалка для интервью

30-секундный ответ для собеседования

«OutOfMemoryError указывает на исчерпание конкретной области памяти, зашифрованной в тексте ошибки. Java heap space означает нехватку кучи для обычных объектов, а GC overhead limit exceeded сигнализирует о том, что сборщик тратит более 98% времени на очистку менее 2% памяти. Ошибки Metaspace и Compressed class space возникают при утечках загрузчиков классов в нативной памяти. Direct buffer memory сообщает о переполнении off-heap памяти NIO (Netty). А unable to create new native thread указывает на исчерпание ресурсов операционной системы: лимита потоков ядра (ulimit -u) или нехватки RAM под стеки потоков -Xss».

Вопросы повышенной сложности с ответами

  1. Почему при ошибке unable to create new native thread увеличение размера -Xmx только ухудшит ситуацию? Ответ: Ошибка unable to create new native thread вызывается ядром ОС, когда не удается выделить память под стек нового потока (-Xss, по умолчанию 1 МБ) или исчерпаны системные лимиты. Стеки потоков аллоцируются в нативной памяти операционной системы, за пределами Java Heap. Увеличивая -Xmx, мы отдаем куче больше виртуального адресного пространства и физической памяти, оставляя ОС и нативным потокам ещё меньше свободного пространства. Для решения проблемы нужно наоборот уменьшать -Xmx, использовать пулы потоков, уменьшать размер стека потока (-Xss512k) или переходить на виртуальные потоки (Virtual Threads / Project Loom).

  2. Чем отличается OutOfMemoryError: Metaspace от OutOfMemoryError: Compressed class space? Ответ: Metaspace хранит все метаданные классов (методы, аннотации, байткод, таблицы виртуальных методов) в нативной памяти процесса. Compressed class space — это специальная строго непрерывная 32-битная адресная область размером по умолчанию 1 ГБ внутри общего метаспейса, в которой хранятся сжатые указатели на структуры Klass для экономии места в заголовках объектов. Если приложение динамически генерирует десятки тысяч уникальных классов, лимит 1 ГБ сжатых классов может закончиться раньше, чем общий лимит Metaspace, порождая специализированную ошибку Compressed class space.

  3. Что делает флаг -XX:-UseGCOverheadLimit и почему его опасно отключать? Ответ: Флаг отключает защитный механизм, выбрасывающий GC overhead limit exceeded, когда сборщик мусора тратит >98% времени на освобождение <2% кучи. Если его отключить, приложение не выбросит ошибку сразу, но зависнет в непрерывных Stop-the-World паузах Full GC. Процессор будет загружен на 100%, сервисные запросы будут отваливаться по таймауту, и через некоторое время приложение всё равно упадёт с ошибкой Java heap space.

  4. Как может произойти Direct buffer memory OOME, если в Heap занято всего 50 МБ из 4 ГБ? Ответ: Прямые буферы (ByteBuffer.allocateDirect()) аллоцируются в нативной памяти ОС через вызов C malloc(). Они находятся вне кучи JVM. При создании буфера HotSpot проверяет лимит -XX:MaxDirectMemorySize. Если сервис (например, на Netty или Kafka) аллоцирует сетевые пакеты в native memory быстрее, чем GC успевает их утилизировать через Cleaner/фантомные ссылки, либо если счетчики ссылок Netty не уменьшаются (ByteBuf.release()), память Direct Memory исчерпывается независимо от степени заполненности Java Heap.

Типичные ошибки и Red Flags

  • ❌ Утверждать, что любой OutOfMemoryError лечится увеличением -Xmx. (Red Flag: непонимание архитектуры памяти, незнание разницы между Heap, Metaspace, Thread Stack и Direct Memory).
  • ❌ Предлагать отключать -XX:-UseGCOverheadLimit как способ решения проблем с производительностью. (Red Flag: непонимание природы трэшинга GC).
  • ❌ Путать StackOverflowError и OutOfMemoryError: unable to create new native thread. (StackOverflowError — это исчерпание глубины стека внутри одного существующего потока из-за рекурсии; unable to create new native thread — это невозможность создать сам поток в ОС).

Связанные темы