← Назад к вопросам

Какая ошибка вылетает, когда заканчивается место в 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
Какая ошибка вылетает, когда заканчивается место в Heap? | PrepBro