ООП ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-07-26

Структура памяти в Java: стек и куча

Стек (stack) и куча (heap) — две основные области памяти, которыми управляет JVM во время работы программы. В куче живут объекты и массивы, в стеке — кадры вызовов методов с локальными переменными и ссылками на эти объекты. Память в куче освобождает сборщик мусора, память в стеке — сама JVM, как только метод завершился.

Структура памяти в Java устроена сложнее, чем «стек и куча», но на старте достаточно разобраться именно с этими двумя областями: через них объясняется почти всё поведение переменных, объектов и сборщика мусора.

1. Куча (heap)

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

Именно в куче работает сборщик мусора (garbage collector): он находит объекты, до которых больше нельзя добраться ни по одной цепочке ссылок, и освобождает занятую ими память. Разработчику не нужно удалять объекты вручную.

Куча одна на всю JVM и общая для всех потоков приложения. Поэтому объект, созданный в одном потоке, может быть прочитан и изменён из другого — и поэтому же общие объекты приходится защищать синхронизацией.

Важное уточнение: объект в куче не «виден отовсюду» сам по себе. Обратиться к нему можно только через ссылку — если ссылку никуда не передали и она пропала, объект становится недостижимым и рано или поздно будет собран сборщиком мусора.

2. Стек (stack)

Стековая память в Java работает по принципу LIFO (last in — first out, «последним зашёл — первым вышел»). При каждом вызове метода на вершине стека создаётся новый кадр стека (stack frame), в котором хранятся:

  • локальные переменные метода примитивных типов (их значения),
  • ссылки на объекты, лежащие в куче,
  • параметры метода и служебная информация для возврата.

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

У каждого потока свой собственный стек. Локальные переменные потока по определению недоступны другим потокам, поэтому они всегда потокобезопасны. Размер стека намного меньше размера кучи: обычно это сотни килобайт — единицы мегабайт на поток.

Ключевое правило: объект всегда создаётся в куче, а в стеке лежит только ссылка на него. Значение ссылочной переменной — это не сам объект, а адрес объекта в куче.

Неочевидный момент

В стеке лежат только локальные примитивы. Примитивные поля объекта (например, int x внутри класса) хранятся вместе с самим объектом — то есть в куче. Правило «примитивы в стеке» работает лишь для переменных внутри метода.

3. Пример: что попадает в стек, а что в кучу

Разберём разницу между примитивной и ссылочной переменной. При объявлении int a = 10 само значение 10 хранится в стеке. При создании Test a = new Test() объект размещается в куче, а переменная a находится в стеке и содержит ссылку на этот объект:

Структура памяти в Java: примитивная переменная в стеке, объект в куче

Теперь то же самое в коде:

public class MemoryDemo {

    static class Point {
        int x;   // поле объекта - хранится в куче вместе с объектом
        int y;
    }

    public static void main(String[] args) {
        int a = 10;              // значение 10 лежит в кадре стека метода main
        Point p = new Point();   // объект Point - в куче, ссылка p - в стеке
        p.x = 5;                 // изменяем поле объекта в куче

        modify(p);               // передаём копию ссылки

        System.out.println(a);   // 10
        System.out.println(p.x); // 42 - метод изменил тот же объект в куче
    }

    static void modify(Point point) {  // новый кадр стека
        point.x = 42;                  // point и p указывают на один объект
    }                                  // кадр снимается со стека
}

Пример показывает сразу три вещи: значение примитива копируется, ссылка тоже копируется (Java всегда передаёт аргументы по значению), но копия ссылки указывает на тот же самый объект в куче — поэтому изменения полей видны в вызывающем методе.

4. Стек и куча: сравнение

Критерий Стек (stack) Куча (heap)
Что хранится Кадры методов: локальные примитивы, ссылки, параметры Объекты, массивы, поля объектов
Кому принадлежит Свой стек у каждого потока Одна на всю JVM, общая для всех потоков
Время жизни данных До выхода из метода Пока на объект есть достижимые ссылки
Освобождение памяти Автоматически, снятием кадра Сборщиком мусора
Размер Небольшой, задаётся ключом -Xss Значительно больше, задаётся -Xms / -Xmx
Скорость доступа Очень быстрая Медленнее: нужен поиск по ссылке и работа GC
Ошибка при переполнении StackOverflowError OutOfMemoryError: Java heap space

5. Metaspace и другие области памяти JVM

Стек и куча — не вся картина. Современная JVM (HotSpot) использует ещё несколько областей:

  • Metaspace — метаданные загруженных классов и методов. Начиная с Java 8 они хранятся в нативной памяти операционной системы, а не в куче.
  • Пул строк (String pool) — с Java 7 находится в куче, поэтому строковые литералы тоже подлежат сборке мусора.
  • Code Cache — машинный код, сгенерированный JIT-компилятором.
  • Регистры PC и стек нативных методов — служебные области, свои у каждого потока.

Что устарело

Формулировка «классы хранятся в куче, в области PermGen» относится к Java 7 и старше. В Java 8 PermGen удалён, метаданные классов переехали в Metaspace вне кучи, а ключи -XX:PermSize и -XX:MaxPermSize больше не поддерживаются — вместо них используется -XX:MaxMetaspaceSize.

6. StackOverflowError и OutOfMemoryError

Две классические ошибки памяти напрямую связаны с двумя областями.

StackOverflowError возникает, когда закончилось место в стеке потока. Самая частая причина — рекурсия без условия выхода: каждый вызов добавляет кадр, и стек переполняется.

public class StackOverflowDemo {
    static int depth = 0;

    static void recurse() {
        depth++;
        recurse();   // нет условия выхода
    }

    public static void main(String[] args) {
        try {
            recurse();
        } catch (StackOverflowError e) {
            System.out.println("Глубина рекурсии: " + depth);
        }
    }
}

OutOfMemoryError: Java heap space возникает, когда в куче не осталось места для нового объекта, а сборщик мусора не может ничего освободить — например, из-за коллекции, которая бесконечно растёт и удерживает ссылки на объекты (утечка памяти).

Размеры областей задаются при запуске JVM:

java -Xms256m -Xmx1g -Xss512k -XX:MaxMetaspaceSize=256m MyApp

Здесь -Xms — начальный размер кучи, -Xmx — максимальный, -Xss — размер стека одного потока, -XX:MaxMetaspaceSize — предел Metaspace.

Часто задаваемые вопросы

Где хранятся поля объекта: в стеке или в куче?

Все поля объекта — и ссылочные, и примитивные — хранятся внутри самого объекта, то есть в куче. В стеке оказываются только локальные переменные метода и ссылки на объекты.

Где хранятся статические поля и константы?

Метаданные класса (структура, байт-код методов) находятся в Metaspace, а значения статических полей хранятся в объекте java.lang.Class, который лежит в куче. Объекты, на которые ссылаются статические поля, тоже находятся в куче и не собираются мусорщиком, пока класс загружен.

Java передаёт объекты по ссылке?

Нет. Java всегда передаёт аргументы по значению. Для ссылочного типа копируется значение ссылки, поэтому метод может изменить поля объекта в куче, но не может заставить переменную вызывающего кода указывать на другой объект.

Всегда ли объект создаётся в куче?

С точки зрения модели памяти Java — да. Но JIT-компилятор умеет делать escape analysis: если объект не покидает метод, он может быть разложен на отдельные поля и размещён в стеке или регистрах. Это оптимизация времени выполнения, на исходный код она не влияет.

Почему стек потокобезопасен, а куча нет?

У каждого потока собственный стек, поэтому его локальные переменные недоступны другим потокам. Куча одна на всю JVM, и один и тот же объект могут одновременно менять несколько потоков — отсюда необходимость в synchronized, volatile и других средствах синхронизации.

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.