Как устроена модель памяти JVM (Java Virtual Machine)?

Ответ

Память JVM разделена на несколько областей (Runtime Data Areas), каждая из которых служит определенной цели в жизненном цикле программы.

Основные области памяти:

  1. Heap (Куча)

    • Назначение: Хранит все объекты и их поля (экземпляры классов), массивы.
    • Управление: Сборщик мусора (Garbage Collector, GC) автоматически освобождает память от неиспользуемых объектов.
    • Структура (поколения):
      • Young Generation: Для новых объектов. Состоит из Eden и двух Survivor Spaces (S0, S1). Частый Minor GC.
      • Old Generation (Tenured): Для долгоживущих объектов, переживших несколько сборок в Young Gen. Редкий, но долгий Major GC (Full GC).
  2. JVM Stack (Стек)

    • Назначение: Хранит фреймы методов (локальные переменные, ссылки на объекты в Heap, частичные результаты).
    • Особенность: Создается для каждого потока. При переполнении возникает StackOverflowError.
  3. Metaspace (Java 8+)

    • Назначение: Хранит метаданные классов (структуры классов, байт-код, константы времени компиляции).
    • Важно: Заменяет PermGen (Permanent Generation). Память выделяется из Native Memory ОС, что снижает риск OutOfMemoryError: PermGen space.
  4. Native Memory (Память ОС)

    • Назначение: Используется самой JVM (Metaspace, код JIT-компилятора) и нативными библиотеками (например, через ByteBuffer.allocateDirect()).

Визуализация и пример:

JVM Memory
├── Heap (Управляется GC)
│   ├── Young Generation
│   │   ├── Eden
│   │   └── Survivor 0/1
│   └── Old Generation
├── JVM Stack (по одному на поток)
├── Metaspace (Классы, методы)
└── Native Memory (JVM internal, NIO)

Пример утечки памяти (Memory Leak):

public class MemoryLeak {
    private static final List<byte[]> LEAK = new ArrayList<>();
    public static void main(String[] args) {
        while (true) {
            // Новый объект создается, но ссылка на него хранится в статическом списке.
            // GC не может удалить эти объекты -> Heap заполняется -> OutOfMemoryError.
            LEAK.add(new byte[10_000_000]); // 10 MB
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

Почему это важно? Понимание модели памяти помогает писать эффективный код, настраивать параметры JVM (-Xmx, -Xms) и диагностировать проблемы производительности.

Ответ 18+ 🔞

А, слушай, про память JVM! Ну это, блядь, целая вселенная, где каждый байт знает своё место, а если не знает — получает пизды от сборщика мусора. Сейчас разложу по полочкам, как в хорошем бардаке.

Вот представь: JVM — это такой охуенно организованный склад. И на нём есть несколько ключевых зон, куда всё складывается.

Основные закутки этого склада:

  1. Heap (Куча)

    • Для чего: Сюда, сука, летят все объекты, которые ты создаёшь через new, и все массивы. Все эти твои User, Order, List<String> — их трупы тут и плавают.
    • Кто убирает: Тут работает главный дворник — Garbage Collector (GC). Он ходит и смотрит: если на объект никто не ссылается (типа, все про него забыли), он его в мусорку. Автоматом, блядь!
    • Внутренний расклад:
      • Young Generation (Детсад): Сюда попадают свеженькие, только что родившиеся объекты. Состоит из Eden (райский сад, где всё начинается) и двух Survivor Spaces (S0, S1, куда переезжают выжившие после первой чистки). Тут частые, но быстрые уборки — Minor GC.
      • Old Generation (Геронтологическое отделение): Сюда переводятся объекты-долгожители, которые пережили несколько чисток в молодёжке. Уборки тут редкие, но, ёпта, капитальные — Major GC (или Full GC), которые могут всё заморозить.
  2. JVM Stack (Стек)

    • Для чего: Это, блядь, как блокнот для каждого потока. В нём хранятся фреймы методов — локальные переменные, ссылки на объекты из кучи, промежуточные результаты вычислений. Вызвал метод — новый листок в блокноте. Закончил метод — листок вырвал и выкинул.
    • Подвох: Блокнот не резиновый. Если рекурсию без тормозов написать и листков станет овердохуища — получишь StackOverflowError. Всё, пиздец, поток.
  3. Metaspace (Святая святых, с Java 8+)

    • Для чего: Тут живут не объекты, а метаданные классов — их структура, байт-код, константы. Раньше это была PermGen, которая постоянно дохла с ошибкой OutOfMemoryError: PermGen space. Теперь это Metaspace — память под неё берётся прямо из Native Memory ОС, и лимиты пошире. Умнее, блядь, стали.
  4. Native Memory (Память ОС)

    • Для чего: Это уже за пределами JVM, но она ей активно пользуется. Сама JVM тут хранит свои внутренние дела (ту же Metaspace, код от JIT-компилятора). А ещё, например, когда ты делаешь ByteBuffer.allocateDirect() для быстрого I/O — память режется прямо тут, минуя кучу.

Как это всё, блядь, выглядит в голове:

Память JVM
├── Куча (Heap) (Царство GC)
│   ├── Молодое поколение (Young Gen)
│   │   ├── Эдем (Eden) - тут рождается всё
│   │   └── Выжившие 0/1 (Survivor) - тут крутятся стойкие
│   └── Старое поколение (Old Gen) - тут доживают ветераны
├── Стек (JVM Stack) (По одному на поток)
├── Metaspace (Тут живут чертежи классов)
└── Native Memory (Территория ОС и нативных штук)

А вот, сука, классический пример, как всё проебать — утечка памяти (Memory Leak):

public class MemoryLeak {
    private static final List<byte[]> LEAK = new ArrayList<>(); // Статический список — он живёт вечно.
    public static void main(String[] args) {
        while (true) {
            // Каждую итерацию создаём здоровенный массив...
            // И кладём ссылку на него в бессмертный статический список LEAK.
            // GC смотрит: "О, на объект есть ссылка из статического поля! Удалять низя!".
            // Куча медленно, но верно заполняется трупами массивов -> OutOfMemoryError.
            LEAK.add(new byte[10_000_000]); // 10 МБ в петлю
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

Нахуя это всё знать? Да чтобы не быть, прости господи, распиздяем! Понимая это, ты:

  1. Пишешь код, который не сосёт память как пылесос.
  2. Можешь настроить JVM флажками (-Xmx, -Xms), чтобы твоё приложение не дохло на ровном месте.
  3. Диагностируешь, почему всё вдруг встало колом — может, это Full GC часами ебётся, а ты и не в курсе.

Короче, память — не хуй собачий, её надо уважать.