Які типи 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. - Причини:
- Досягнуто ліміту потоків процесу або користувача в ОС (
ulimit -uабо/proc/sys/kernel/threads-max). - Досягнуто системного ліміту віртуальних областей пам’яті
vm.max_map_count(кожен стек потоку вимагає 2 областіmmap— стек і guard page). - Недостатньо вільної оперативної пам’яті під стеки потоків (
-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».
Питання підвищеної складності з відповідями
- Чому при помилці
unable to create new native threadзбільшення розміру-Xmxлише погіршить ситуацію?- Відповідь: Помилка
unable to create new native threadвикликається ядром ОС, коли не вдається виділити пам’ять під стек нового потоку (-Xss, за замовчуванням 1 МБ) або вичерпано системні ліміти. Стеки потоків алокуються в нативній пам’яті операційної системи, за межами Java Heap. Збільшуючи-Xmx, ми віддаємо купі більше віртуального адресного простору та фізичної пам’яті, залишаючи ОС та нативним потокам ще менше вільного простору. Для вирішення проблеми потрібно навпаки зменшувати-Xmx, використовувати пули потоків, зменшувати розмір стека потоку (-Xss512k) або переходити на віртуальні потоки (Virtual Threads / Project Loom).
- Відповідь: Помилка
- Чим відрізняється
OutOfMemoryError: MetaspaceвідOutOfMemoryError: Compressed class space?- Відповідь: Metaspace зберігає всі метадані класів (методи, анотації, байткод, таблиці віртуальних методів) у нативній пам’яті процесу.
Compressed class space— це спеціальна суворо неперервна 32-бітна адресна область розміром за замовчуванням 1 ГБ усередині загального метаспейсу, в якій зберігаються стиснуті покажчики на структуриKlassдля економії місця в заголовках об’єктів. Якщо застосунок динамічно генерує десятки тисяч унікальних класів, ліміт 1 ГБ стиснутих класів може закінчитися раніше за загальний ліміт Metaspace, породжуючи спеціалізовану помилкуCompressed class space.
- Відповідь: Metaspace зберігає всі метадані класів (методи, анотації, байткод, таблиці віртуальних методів) у нативній пам’яті процесу.
- Що робить прапорець
-XX:-UseGCOverheadLimitі чому його небезпечно вимикати?- Відповідь: Прапорець вимикає захисний механізм, що викидає
GC overhead limit exceeded, коли збирач сміття витрачає >98% часу на звільнення <2% купи. Якщо його вимкнути, застосунок не викине помилку одразу, але зависне в безперервних Stop-the-World паузах Full GC. Процесор буде завантажений на 100%, запити клієнтів відпадатимуть за таймаутом, і через деякий час застосунок все одно впаде з помилкоюJava heap space.
- Відповідь: Прапорець вимикає захисний механізм, що викидає
- Як може статися
Direct buffer memoryOOME, якщо в Heap зайнято лише 50 МБ із 4 ГБ?- Відповідь: Прямі буфери (
ByteBuffer.allocateDirect()) алокуються в нативній пам’яті ОС через виклик Cmalloc(). Вони розташовані поза купою 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— це неможливість створити сам потік в ОС).