Как происходит аллокация памяти перед выбрасыванием OutOfMemoryError в Java?

Ответ

OutOfMemoryError (OOM) — критическая ошибка, возникающая, когда JVM не может выделить память для нового объекта, и сборщик мусора (GC) не может освободить достаточное количество памяти.

Процесс аллокации и эскалации до OOM:

  1. Запрос на аллокацию: JVM пытается выделить память в куче (heap) для создания нового объекта.
  2. Нехватка памяти: Если в текущем поколении (Eden Space) недостаточно свободного непрерывного пространства, запускается малая сборка мусора (Minor GC).
  3. Эскалация: Если после Minor GC памяти всё ещё не хватает, JVM пытается выделить объект непосредственно в старом поколении (Old Generation). При неудаче запускается полная сборка мусора (Full GC).
  4. Расшение кучи: Если после Full GC памяти недостаточно, JVM пытается расширить размер кучи в пределах, заданных параметром -Xmx (максимальный размер).
  5. Исключение: Если расширение невозможно (достигнут -Xmx) или не помогает, JVM выбрасывает OutOfMemoryError.

Пример, вызывающий OOM в куче:

// Запуск: java -Xmx10m -Xms10m OOMDemo
import java.util.ArrayList;
import java.util.List;

public class OOMDemo {
    public static void main(String[] args) {
        List<Object> leak = new ArrayList<>();
        while (true) {
            leak.add(new byte[1024 * 1024]); // Аллокация по 1 МБ
        }
    }
}

Основные типы OutOfMemoryError:

  • Java heap space — классическая нехватка памяти в куче для объектов.
  • Metaspace — исчерпана память для метаданных классов (заменяет PermGen).
  • Direct buffer memory — переполнена память, выделенная для NIO ByteBuffer.allocateDirect().
  • Unable to create new native thread — превышен лимит на количество потоков ОС.
  • Requested array size exceeds VM limit — попытка создать массив размером больше Integer.MAX_VALUE - 2 или доступной памяти.

Best Practice: Для диагностики используйте профилировщики (VisualVM, JProfiler) и анализируйте дампы кучи, создаваемые с помощью параметров -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof.

Ответ 18+ 🔞

А, блядь, OutOfMemoryError! Ну это ж классика, ёпта, как борщ со сметаной. Это когда твоя Java-машина (JVM), такая, понимаешь, мартышлюшка, уже не может нигде найти местечка для новой переменной, и даже сборщик мусора, этот последний уборщик, размахивающий метлой, нихуя не может подмести.

Как всё это красиво разваливается, шаг за шагом:

  1. Хочу объект! Программа орёт: «Дай мне памяти для новой штуки!». JVM лезет в основную кучу (heap), как в карман.
  2. Места нет, сука. А в кармане, в молодом поколении (Eden Space), уже битком. JVM говорит: «Ладно, щас быстренько приберусь» — и запускает малую уборку (Minor GC). Выкидывает мусор, сдвигает что осталось.
  3. Всё равно не лезет. Если после этой суеты места всё равно нет, она пытается сунуть объект прямиком в старое поколение (Old Generation), типа в кладовку. Не получается? Ну всё, пиздец, объявляется генеральная уборка (Full GC). Весь дом перетряхивается.
  4. Расширяем владения. После генералки всё ещё тесно? JVM начинает раздвигать стены кучи, как живот у меня после пельменей, но только до предела, который ты ей задал (-Xmx).
  5. Финал. Если расширяться некуда или не помогло — BANG! OutOfMemoryError прямо в ебало. Всё, приехали.

Вот тебе наглядный пиздец, который это вызывает:

// Запускай так: java -Xmx10m -Xms10m OOMDemo
import java.util.ArrayList;
import java.util.List;

public class OOMDemo {
    public static void main(String[] args) {
        List<Object> leak = new ArrayList<>();
        while (true) {
            leak.add(new byte[1024 * 1024]); // Создаём по 1 МБ, пока не лопнем
        }
    }
}

А ещё эта ошибка, блядь, бывает разная, как настроение у тещи:

  • Java heap space — самый родной, классический. Место в основной куче для объектов кончилось. Овердохуища всего наворотили.
  • Metaspace — память для всяких служебных описаний классов (раньше PermGen была) забили. Классов насоздавали — хоть святых выноси.
  • Direct buffer memory — это когда нажрались памяти мимо кучи, через ByteBuffer.allocateDirect(). Своего рода чёрный нал.
  • Unable to create new native thread — операционка говорит: «Хуй тебе, а не ещё один поток!». Лимит исчерпан.
  • Requested array size exceeds VM limit — ну это совсем уже, запросили массив размером с Луну. Даже JVM офигела.

Как не быть мудаком? Когда это случится (а случится), не охуевай просто так. Настрой дамп кучи на вылет: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/куда/положить/dump.hprof. Потом этот дамп в профилировщик (типа VisualVM) — и смотри, кто там столько памяти жрёт, хитрая жопа.