🧠 Розділ 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 та аналіз завантажувачів класів у профілювальнику.

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 — це неможливість створити сам потік в ОС).

Пов’язані теми