Що станеться при OutOfMemoryError?
4. Потік, що запросив пам'ять → помирає
🟢 Junior Level
OutOfMemoryError (OOME) — помилка, коли Java не може виділити пам’ять для об’єкта.
Що відбувається:
- Спроба виділити пам’ять → немає місця
- GC намагається очистити → все ще немає місця
- Видається OutOfMemoryError
- Потік, що запросив пам’ять → помирає
Види OOME:
Java heap space— Heap переповненийMetaspace— метадані класів переповненіGC overhead limit exceeded— GC працює постійно
🟡 Middle Level
Механіка OOME
1. Allocation Failure → немає пам'яті
2. Full GC → намагається очистити
3. SoftReference очищені → все ще немає
4. GC Overhead Limit — ЦЕ КОНКРЕТНИЙ тип OOME (GC overhead limit exceeded).
Спрацьовує, коли JVM витрачає >98% часу на GC і звільняє <2% Heap.
НЕ всі OOME проходять через цю перевірку — це окремий heuristic.
5. → OutOfMemoryError!
Zombie State
OOME може статися в БУДЬ-ЯКОМУ місці, включаючи виділення ресурсу.
Якщо OOME трапився при відкритті файлу → файл може залишитися відкритим.
Якщо OOME при захопленні lock → lock не буде звільнений.
⚠️ finally-блоки можуть НЕ виконатися, якщо OOME відбувається при виділенні пам'яті для самого фрейму стека.
OOME НЕ вбиває JVM миттєво!
→ Помирає тільки потік, що запросив пам'ять
→ Решта потоків працюють
→ АЛЕ: стан може бути corrupted!
JVM OOME vs OS OOM Killer
JVM OOME:
→ Бачите стек-трейс у логах
→ Є дамп хіпу
OS OOM Killer (Docker/K8s):
→ Процес просто зникає
→ У dmesg: "Out of memory: Kill process"
→ Немає логів Java!
→ Причина: RSS > ліміт контейнера
Production: Fail-Fast
# Обов'язково у production:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/dumps/
-XX:+ExitOnOutOfMemoryError
# → При OOME: дамп + вихід
# → Оркестратор перезапустить
# → Без zombie state
🔴 Senior Level
Thread Death і Corruption
OOME у потоці, що тримає Lock:
→ Lock не відпущений
→ Решта потоків чекають вічно
→ Deadlock!
OOME у ConcurrentHashMap:
→ Map у проміжному стані
→ Подальші операції → undefined behavior
Native Memory OOME
DirectByteBuffer витік:
→ Heap порожній
→ Native Memory = 100%
→ OOM Killer вбиває процес
→ Немає логів Java!
Діагностика:
-XX:NativeMemoryTracking=detail
dmesg | grep -i oom
Діагностика за типами
| Тип помилки | Причина | Рішення |
|---|---|---|
| Heap space | Витік або маленький -Xmx | MAT аналіз дампа |
| GC overhead | Хіп забитий впритул | GC Logs, оптимізація |
| Metaspace | Витік ClassLoader | jcmd VM.metaspace |
| Direct buffer | Витік Netty/NIO | NMT |
| Native thread | Ліміт потоків ОС | ulimit -u |
Heap Dump аналіз
# Автоматичний дамп при OOME
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dumps/
# Аналіз в Eclipse MAT:
# → Dominator Tree
# → Path to GC Roots
# → Знайти витік
Best Practices
- Ніколи не ловіть OOME у бізнес-коді
- ExitOnOutOfMemoryError у production
- HeapDumpOnOutOfMemoryError — обов’язково
- HeapDumpPath → зовнішній volume
- Моніторьте RSS і Native Memory
- NMT для off-heap витоків
- Fail-fast > zombie state
Резюме для Senior
- OOME ≠ миттєва смерть JVM
- Zombie state = corrupted locks і структури
- OS OOM Killer = немає логів, процес зникає
- Fail-fast = ExitOnOutOfMemoryError
- Heap Dump = головний інструмент діагностики
- Native Memory ≠ Heap (потрібен NMT)
- Ніколи не catch OOME
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- OOME: JVM не може виділити пам’ять → GC намагається очистити → SoftReference очищені → все ще немає → OutOfMemoryError
- OOME вбиває тільки потік, що запросив, НЕ всю JVM; решта потоків працюють у corrupted state (zombie state)
- Zombie state: OOME при захопленні lock → lock не відпущений → deadlock; OOME у ConcurrentHashMap → undefined behavior
- JVM OOME vs OS OOM Killer: JVM OOME дає стек-трейс і дамп; OS OOM Killer (Docker/K8s) — процес зникає, немає логів
- GC Overhead Limit: окремий тип OOME; спрацьовує, коли JVM витрачає >98% часу на GC і звільняє <2% Heap
ExitOnOutOfMemoryError+HeapDumpOnOutOfMemoryError— обов’язково у production: дамп + вихід → оркестратор перезапустить- finally-блоки можуть НЕ виконатися, якщо OOME відбувається при виділенні пам’яті для самого фрейму стека
Часті уточнюючі запитання:
- Чому не можна ловити OOME у бізнес-коді? — Стан corrupted: lock не відпущений, Map у проміжному стані; додаток ненадійний
- Чим JVM OOME відрізняється від OS OOM Killer? — JVM OOME: бачите стек-трейс, є дамп; OS OOM Killer:
dmesg | grep oom, процес убитий через SIGKILL, немає логів Java - Що таке zombie state? — JVM продовжує працювати після OOME одного потоку, але з corrupted locks/структурами → undefined behavior
- Чому HeapDumpPath має бути на external volume? — У Kubernetes ефемерне сховище заб’ється дампом → под не перезапуститься
Червоні прапорці (НЕ говорити):
- «Ловлю OOME і продовжую роботу» — zombie state, corrupted дані; краще fail-fast
- «OOME миттєво вбиває JVM» — помирає тільки потік; JVM продовжує працювати у corrupted state
- «OS OOM Killer — це проблема Java» — це рішення ОС; причина: RSS > ліміт контейнера, не
-Xmx
Пов’язані теми:
- [[20. Які типи OutOfMemoryError існують]]
- [[21. Що таке витік пам’яті і як його виявити]]
- [[23. Що таке heap dump]]
- [[18. Що таке параметри -Xms та -Xmx]]
- [[6. Що таке витік пам’яті в Java]]