← Назад к вопросам
Какая ошибка вылетает, когда заканчивается место в Heap?
3.0 Senior🔥 231 комментариев
#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
OutOfMemoryError — когда в Heap заканчивается место
OutOfMemoryError (OOM) — это не исключение в классическом смысле, а Error (наследник Throwable). Это очень важное различие, так как OOM часто невозможно обработать.
Иерархия исключений
Throwable
├── Exception (проверяемые исключения)
│ ├── IOException
│ ├── SQLException
│ └── ...
└── Error (ошибки VM - обычно неустранимы)
├── OutOfMemoryError
├── StackOverflowError
├── VirtualMachineError
└── ...
Важно: Error не нужно ловить в коде (да, можно, но это плохая практика).
Основные типы OutOfMemoryError
1. Java heap space (самый частый)
// ❌ Код, вызывающий OOM
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 1MB на каждой итерации
// java.lang.OutOfMemoryError: Java heap space
}
Стектрейс:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:48)
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
2. PermGen space / Metaspace (в Java 8+)
// ❌ Динамическое создание классов
public class MetaspaceOOM {
static List<Class<?>> classes = new ArrayList<>();
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
int count = 0;
while (true) {
CtClass cc = pool.makeClass("Generated_" + count++);
Class<?> clazz = cc.toClass();
classes.add(clazz);
// java.lang.OutOfMemoryError: Metaspace
}
}
}
// Причины:
// - Много ClassLoader'ов
// - Динамическая генерация классов (Javassist, CGLIB)
// - Утечки в Bean Definition Registry (Spring)
3. Unable to create new native thread
// ❌ Создание слишком много потоков
public class ThreadOOM {
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
new Thread(() -> {
try { Thread.sleep(Long.MAX_VALUE); }
catch (InterruptedException e) {}
}).start();
count++;
System.out.println("Created thread #" + count);
// java.lang.OutOfMemoryError: Unable to create new native thread
}
}
}
4. Direct buffer memory (ByteBuffer outside heap)
// ❌ Слишком много DirectBuffer
while (true) {
ByteBuffer.allocateDirect(10 * 1024 * 1024); // 10MB DirectBuffer
// java.lang.OutOfMemoryError: Direct buffer memory
}
Как отловить OOM (если очень нужно)
public class OOMHandler {
public static void main(String[] args) {
try {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]);
}
} catch (OutOfMemoryError e) {
System.err.println("Caught OOM: " + e.getMessage());
e.printStackTrace();
// Обычно после OOM состояние VM нестабильно!
// Лучше сразу завершить процесс
System.exit(1);
}
}
}
Почему это плохая идея:
- После OOM, JVM в нестабильном состоянии
- GC может не помочь
- Следующая попытка выделить память может привести к краху
Heap размер и JVM параметры
# Минимум и максимум Heap
java -Xms512m -Xmx2048m MyApp
# -Xms512m = Initial Heap Size = 512 MB
# -Xmx2048m = Maximum Heap Size = 2048 MB
# Если нужно больше памяти:
java -Xms2g -Xmx8g MyApp # 2GB - 8GB
Анализ OutOfMemoryError с Heap Dump
# Генерировать heap dump при OOM
java -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/dumps \
MyApp
# Или забрать dump из работающего процесса
jcmd <pid> GC.heap_dump /tmp/heap.hprof
# Анализировать с помощью MAT (Memory Analyzer Tool)
# или Eclipse IDE
Профилактика (лучше, чем лечение)
// 1. Мониторить использование памяти
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
long usedMemory = heapUsage.getUsed();
long maxMemory = heapUsage.getMax();
long percentUsed = (usedMemory / maxMemory) * 100;
if (percentUsed > 85) {
logger.warn("Heap usage is high: {}%", percentUsed);
}
// 2. Избегать утечек памяти
// ❌ Плохо - listener никогда не удаляется
button.addActionListener(e -> doSomething());
// ✅ Хорошо - явно удалить listener
ActionListener listener = e -> doSomething();
button.addActionListener(listener);
// ...
button.removeActionListener(listener);
// 3. Использовать слабые ссылки для кэшей
Map<String, Object> cache = new WeakHashMap<>();
cache.put("key", new HeavyObject()); // Удалится при GC если не используется
// 4. Правильно работать с коллекциями
List<String> list = new ArrayList<>();
list.add("data");
list.clear(); // Очистить, но ёмкость может остаться
// Если список не нужен совсем:
list = null; // GC может забрать
GC логирование для отладки
# Логирование сборки мусора
java -XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps \
-Xloggc:/var/log/gc.log \
MyApp
# Java 9+
java -Xlog:gc*:file=gc.log:time,uptime,level,tags MyApp
Важные выводы
- OutOfMemoryError вылетает при нехватке памяти в Heap
- Это Error, а не Exception - обычно неустранимо
- Есть разные типы OOM: heap space, Metaspace, threads, direct buffer
- Ловить OOM - плохая практика (кроме логирования и graceful shutdown)
- Профилактика: мониторинг памяти, предотвращение утечек, правильный размер Heap
- Анализ: heap dump + MAT для поиска утечек
- Используй WeakHashMap для кэшей, которые должны очищаться при GC