JavaRush /Курсы /JAVA 25 SELF /Устройство памяти в JVM: стек, куча, PermGen/MetaSpace

Устройство памяти в JVM: стек, куча, PermGen/MetaSpace

JAVA 25 SELF
64 уровень , 0 лекция
Открыта

1. Обзор памяти процесса Java

Когда вы запускаете Java-программу, JVM (Java Virtual Machine) просит у операционной системы кусочек памяти. Иногда — скромный, иногда — весьма внушительный (особенно если вы запускаете какой-нибудь Minecraft с кучей модов). Эта память делится на несколько ключевых областей, каждая из которых играет свою роль:

  • Стек (Stack) — для локальных переменных и вызовов методов.
  • Куча (Heap) — для всех объектов, которые вы создаёте через new.
  • Служебные области (PermGen/MetaSpace) — для метаданных классов, статических полей и прочих «магических» вещей.

Выглядит это примерно так:

┌───────────────────────────────┐
│ JVM-процесс                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Каждый поток — свой стек!
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Общий для всех потоков
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ PermGen/      │ ← Метаданные классов
│ │ MetaSpace     │
│ └───────────────┘             │
└───────────────────────────────┘

Почему это важно?

  • Понимание устройства памяти помогает писать более эффективный и безопасный код.
  • Проще диагностировать ошибки типа StackOverflowError или OutOfMemoryError.
  • Не страшны слова «сборщик мусора» и «утечка памяти» — вы понимаете, где и что искать.

2. Стек (Stack): быстро, локально, но не навсегда

Стек — это специальная область памяти, выделяемая для каждого потока отдельно. Стек похож на стопку тарелок: последнюю положили — первую заберёте только после того, как снимете все остальные. То есть стек работает по принципу LIFO (Last In, First Out).

Зачем нужен стек?

В стеке хранятся:

  • Локальные переменные методов (например, int x = 5; внутри метода).
  • Адрес возврата после вызова метода (чтобы знать, куда вернуться после завершения работы метода).

Каждый раз, когда вы вызываете метод, в стек добавляется новый фрейм (stack frame) — нечто вроде коробки, в которой лежат все локальные переменные этого метода и служебная информация. Когда метод заканчивает работу, его фрейм удаляется — все его локальные переменные исчезают.

Пример

public static void main(String[] args) {
    int a = 10;           // a лежит в стеке main
    int b = sum(a, 5);    // вызываем sum
}

public static int sum(int x, int y) {
    int result = x + y;   // x, y, result лежат в стеке sum
    return result;
}
  • Когда вызывается sum, для него создаётся отдельный фрейм в стеке.
  • После завершения работы sum его переменные исчезают.

Жизненный цикл переменной

Локальные переменные живут только до тех пор, пока выполняется метод, в котором они объявлены. Как только метод завершился — их уже нет, память освобождена мгновенно.

Переполнение стека

Если вы случайно (или намеренно) написали бесконечную рекурсию, каждый вызов метода будет добавлять новый фрейм в стек. В какой-то момент стек закончится, и вы получите:

Exception in thread "main" java.lang.StackOverflowError

Пример:

public static void main(String[] args) {
    recurse();
}
public static void recurse() {
    recurse(); // Бесконечная рекурсия!
}

Размер стека

Размер стека ограничен — обычно это несколько мегабайт на поток (можно задать параметром -Xss). Если стек закончился — программа падает с ошибкой.

3. Куча (Heap): место для ваших объектов

Куча (Heap) — это общая область памяти для всех потоков, где живут все объекты, которые вы создаёте с помощью new, а также массивы. Именно в куче происходит вся магия объектно-ориентированного программирования.

Как объекты попадают в кучу?

String s = new String("Hello");
int[] arr = new int[10];
  • Переменная s — это ссылка, она лежит в стеке.
  • Сам объект String и массив arr — лежат в куче.

Жизненный цикл объекта

Объект живёт в куче до тех пор, пока на него существует хотя бы одна сильная ссылка (strong reference). Как только на объект никто не ссылается — он становится «мусором» и может быть удалён сборщиком мусора (GC).

Управление памятью

В отличие от C/C++, где вы сами должны заботиться об освобождении памяти (free, delete), в Java этим занимается GC. Вы не можете явно освободить объект, но можете обнулить все ссылки на него — тогда он станет кандидатом на удаление.

Схема: где что лежит?

Stack (main)
  └─ s ─┬────────────┐
         │           │
         ▼           │
      Heap           │
   ┌─────────────┐   │
   │ String "Hello"◄──┘
   └─────────────┘

Особенности кучи

  • Куча одна на весь процесс JVM.
  • Размер кучи можно задавать при запуске (-Xmx, -Xms).
  • Если в куче не осталось свободного места и GC не может освободить память — программа падает с ошибкой OutOfMemoryError.

4. PermGen и MetaSpace: где живут классы?

Когда вы пишете class MyClass { ... }, а потом запускаете программу, JVM должна где-то хранить всё, что связано с этим классом — методы, поля, байткод, статические переменные, константы и даже строковые литералы. Для этого в JVM существует особая область памяти, где «живут» классы.

Раньше, до Java 8, эту область называли PermGen (Permanent Generation). Но у неё было немало проблем — например, она имела фиксированный размер, и если места не хватало, приложение просто вылетало с ошибкой OutOfMemoryError: PermGen space.

С выходом Java 8 появилась новая, более гибкая область — MetaSpace. Она заменила старую PermGen и теперь может автоматически расширяться, занимая столько памяти, сколько нужно системе (в пределах доступной физической).

PermGen (до Java 8)

  • В PermGen хранились метаданные классов, статические поля, строковые литералы.
  • Размер PermGen был ограничен (по умолчанию небольшой), можно было увеличить параметром -XX:MaxPermSize=256m.
  • Если в приложение динамически подгружалось много классов (например, в web-серверах), PermGen мог «закончиться», и вы получали ошибку:
java.lang.OutOfMemoryError: PermGen space
  • Проблема: очистка PermGen происходила не всегда корректно, если классы выгружались динамически (например, при перезагрузке web-приложений).

MetaSpace (Java 8+)

  • С Java 8 PermGen исчез и появился MetaSpace.
  • MetaSpace хранит метаданные классов, но теперь в нативной памяти (за пределами Java Heap).
  • Размер MetaSpace по умолчанию не ограничен (ограничивается только памятью системы), но можно задать лимит через -XX:MaxMetaspaceSize=512m.
  • Ошибка при нехватке памяти теперь выглядит так:
java.lang.OutOfMemoryError: Metaspace
  • В MetaSpace также попадают статические поля, методы, информация о классах.

Схема: как всё устроено

┌───────────────────────────────┐
│ JVM-процесс                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Локальные переменные, вызовы методов
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Объекты, массивы, всё, что через new
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ MetaSpace     │ ← Метаданные классов, статические поля
│ └───────────────┘             │
└───────────────────────────────┘

Почему это важно?

Если вы пишете обычные десктопные или серверные приложения, скорее всего, вы никогда не столкнётесь с ошибками PermGen или MetaSpace. Но если работаете с динамической загрузкой классов (например, плагины, web-приложения, фреймворки типа Spring, которые могут подгружать и выгружать кучу классов), то знание про MetaSpace — must-have!

5. Иллюстрация: Схема памяти JVM

flowchart TD
    subgraph JVM
        direction TB
        Stack1["Stack (Thread 1)"]
        Stack2["Stack (Thread 2)"]
        Heap[Heap]
        MetaSpace[MetaSpace]
    end
    Stack1 --ссылается на--> Heap
    Stack2 --ссылается на--> Heap
    Heap --использует классы из--> MetaSpace
  • Каждый поток — свой стек.
  • Все стеки могут ссылаться на объекты в куче.
  • Объекты в куче «знают» свой класс, информация о котором лежит в MetaSpace.

6. Пример: как это выглядит в реальном коде

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 42; // x лежит в стеке main
        String s = "Hello!"; // s — ссылка в стеке, объект String в куче, литерал "Hello!" в MetaSpace
        Person p = new Person("Alice"); // p — ссылка в стеке, объект Person в куче

        // Вызовем метод, чтобы создать новый стек-фрейм
        printPerson(p);
    }

    public static void printPerson(Person person) {
        // person — ссылка в стеке printPerson
        System.out.println(person.getName());
    }
}

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() { return name; }
}

Разбор:

  • x — локальная переменная, живёт в стеке метода main.
  • s — ссылка в стеке, объект String в куче, а строковый литерал "Hello!" — в MetaSpace.
  • p — ссылка в стеке, объект Person в куче.
  • Класс Person и все его методы/поля — в MetaSpace (метаданные классов).
  • Вызов printPerson(p) создаёт новый стек-фрейм; внутри него локальная ссылка person указывает на тот же объект в куче.

7. Как JVM управляет памятью: краткий FAQ

Могу ли я управлять стеком?
Нет, стек полностью под контролем JVM. Вы можете только задавать его размер при запуске (-Xss).

Могу ли я управлять кучей?
Частично: размер кучи задаётся при запуске (-Xmx, -Xms). За очистку отвечает сборщик мусора (GC).

Могу ли я управлять MetaSpace?
Можно ограничить размер (-XX:MaxMetaspaceSize), но обычно это не требуется.

Что происходит при нехватке памяти?
— Если закончился стек — StackOverflowError.
— Если закончилась куча — OutOfMemoryError: Java heap space.
— Если закончился MetaSpace — OutOfMemoryError: Metaspace.

8. Типичные ошибки при работе с памятью

Ошибка №1: StackOverflowError из-за бесконечной рекурсии. Самая частая причина — забыли предусмотреть условие выхода из рекурсии. Например, метод вызывает сам себя без остановки. JVM не сможет расширить стек до бесконечности, и программа «упадёт».

Ошибка №2: OutOfMemoryError из-за переполнения кучи. Если вы создаёте слишком много объектов, на которые продолжают ссылаться переменные/коллекции (например, добавляете элементы в список, но никогда их не удаляете), куча может закончиться.

Ошибка №3: OutOfMemoryError: PermGen space / Metaspace. Если вы используете плагины или динамически подгружаете кучу классов, а MetaSpace не очищается (например, из-за неправильной выгрузки классов), может закончиться место в MetaSpace.

Ошибка №4: Путаница между ссылкой и объектом. Многие начинающие путают: переменная типа Person в стеке — это только ссылка, а сам объект — в куче.

Ошибка №5: Ожидание, что сборщик мусора удалит всё мгновенно. GC работает «по настроению» (на самом деле — по внутренним алгоритмам и при нехватке памяти), а не сразу после того, как вы обнулили ссылку. Не стоит рассчитывать на немедленное освобождение памяти.

1
Задача
JAVA 25 SELF, 64 уровень, 0 лекция
Недоступна
StackOverflowError – Озорной Каскад Рекурсии 🧙‍♂️
StackOverflowError – Озорной Каскад Рекурсии 🧙‍♂️
1
Задача
JAVA 25 SELF, 64 уровень, 0 лекция
Недоступна
OutOfMemoryError – Ненасытный Пожиратель Памяти 💾
OutOfMemoryError – Ненасытный Пожиратель Памяти 💾
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Kenny Уровень 66
27 февраля 2026
String s = "Hello!"; // s — ссылка в стеке, объект String в куче, литерал "Hello!" в MetaSpace. Тут литерал "Hello!" тоже лежит в куче, именно в String Pool. Советую всегда с ии углублять темы, которые тут рассматриваются, а то получаются такие казусы.
Anton Pohodin Уровень 27
29 ноября 2025
Level 64
Andrey Уровень 1
24 ноября 2025
64+
nastya_zhadan Уровень 66
23 ноября 2025
64