Что произойдёт при 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. Что такое memory leak и как его обнаружить]]
- [[23. Что такое heap dump]]
- [[18. Что такое параметры -Xms и -Xmx]]
- [[6. Что такое утечка памяти в Java]]