Перейти к содержанию

Стек (stack) и куча (heap) в Java

Для оптимального запуска приложения JVM делит память на стек (stack) и динамическую память (heap). Всякий раз, когда объявляем новые переменные и объекты, вызываем новый метод, объявляем String или выполняем аналогичные операции, JVM выделяет память для этих операций либо из памяти стека, либо из кучи.

В этом руководстве рассмотрим эти модели памяти. Во-первых, рассмотрим их ключевые особенности. Затем узнаем, как они хранятся в оперативной памяти и где их можно использовать. Наконец, обсудим ключевые различия между ними.

Стек в Java

Стек в Java используется для выделения статической памяти и выполнения потока. Он содержит примитивные значения, характерные для метода, и ссылки на объекты в куче, на которые ссылается метод.

Доступ к этой памяти осуществляется в порядке «последним вошел – первым вышел» (LIFO). Всякий раз, когда вызываем новый метод, поверх стека создается новый блок, который содержит такие значения, характерные для этого метода, как примитивные переменные и ссылки на объекты.

Когда метод завершает выполнение, соответствующий фрейм стека сбрасывается, поток возвращается к вызывающему методу, и освобождается место для следующего метода.

Основные характеристики стека

Некоторые особенности стека:

  • он соответственно увеличивается и уменьшается по мере вызова и возврата новых методов;
  • переменные внутри стека существуют только до тех пор, пока работает создавший их метод;
  • он автоматически выделяется и освобождается, когда метод завершает выполнение;
  • если эта память заполнена, Java выдает ошибку java.lang.StackOverFlowError;
  • доступ к этой памяти быстрее по сравнению с кучей;
  • эта память является потокобезопасной, поскольку каждый поток работает в собственном стеке.

Куча в Java

Куча используется для динамического выделения памяти для объектов Java и классов JRE во время выполнения. Новые объекты всегда создаются в куче, а ссылки на эти объекты хранятся в стеке.

Эти объекты имеют глобальный доступ, и можно получить к ним доступ из любой точки приложения.

Можно разбить эту модель памяти на более мелкие части, называемые поколениями:

Young Generation (молодое поколение) – здесь размещаются и стареют все новые объекты. Минорная сборка мусора происходит, если эта область заполняется.

Old или Tenured Generation (старое или постоянное поколение) – здесь хранятся долгоживущие объекты. Когда объекты хранятся в молодом поколении, устанавливается порог возраста объекта, и когда этот порог достигается, объект перемещается в старое поколение.

Permanent Generation (постоянное поколение) – состоит из метаданных JVM для классов среды выполнения и методов приложения.

Эти части также обсуждаются в статье «Разница между JVM, JRE и JDK».

Можно манипулировать размером кучи в соответствии с нашими потребностями. Для получения дополнительной информации посмотрите эту статью.

Ключевые особенности кучи в Java

Некоторые особенности кучи:

  • доступ осуществляется с помощью сложных методов управления памятью, которые включают в себя Young Generation, Old или Tenured Generation и Permanent Generation;
  • если место в куче заполнено, Java выдает ошибку java.lang.OutOfMemoryError;
  • доступ к этой памяти медленнее, чем к стеку;
  • эта память, в отличие от стека, не освобождается автоматически, ей нужен сборщик мусора, чтобы освободить неиспользуемые объекты для сохранения эффективности использования памяти;
  • в отличие от стека, куча не является потокобезопасной и должна быть защищена путем правильной синхронизации кода.

Пример

Основываясь на полученных знаниях, проанализируем простой код Java, чтобы оценить, как здесь можно управлять памятью:

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class PersonBuilder {
    private static Person buildPerson(int id, String name) {
        return new Person(id, name);
    }

    public static void main(String[] args) {
        int id = 23;
        String name = "John";
        Person person = null;
        person = buildPerson(id, name);
    }
}

Давайте проанализируем шаг за шагом:

  1. Когда входим в метод main(), в стеке создается место для хранения примитивов и ссылок этого метода.
    • Стек напрямую хранит примитивное значение integer id.
    • В стеке также будет создана ссылочная переменная person типа Person, которая будет указывать на реальный объект в куче.
  2. Вызов параметризованного конструктора Person(int, String) из main() выделит дополнительную память поверх предыдущего стека. Она будет хранить:
    • Ссылку на объект this вызывающего объекта в стеке
    • id примитивного значения в стеке
    • Ссылочную переменную String аргумента name, которая будет указывать на фактическую строку из пула строк в куче.
  3. Метод main() – это дальнейший вызов статического метода buildPerson(), для которого дальнейшее выделение будет происходить в стеке поверх предыдущего. Снова будут сохранены переменные в порядке, описанном выше.
  4. Однако в куче будут храниться все переменные экземпляра для вновь созданного объекта person типа Person.

Посмотрим на это распределение на диаграмме ниже:

Резюме

Прежде чем завершим эту статью, кратко суммируем различия между стеком и кучей:

ПараметрСтекКуча
ПриложениеСтек используется частями, по одному во время выполнения потокаВсе приложение использует кучу во время выполнения
РазмерСтек имеет ограничения по размеру в зависимости от ОС и обычно меньше, чем кучаДля кучи нет ограничений по размеру
ХранениеХранит только примитивные переменные и ссылки на объекты, созданные в кучеВсе вновь созданные объекты хранятся здесь
ДоступДоступ к нему осуществляется с использованием системы распределения памяти «последним пришел – первым вышел» (LIFO)Доступ к этой памяти осуществляется с помощью сложных методов управления памятью, включая Young Generation, Old или Tenured Generation и Permanent Generation
Продолжительность жизниСтек существует только до тех пор, пока работает текущий методКуча существует до тех пор, пока работает приложение
ЭффективностьГораздо быстрее выделить по сравнению с кучейМедленнее выделять по сравнению со стеком
Распределение/освобождениеЭта память автоматически выделяется и освобождается при вызове и возврате методаКуча выделяется, когда новые объекты создаются и освобождаются сборщиком мусора, если на них больше нет ссылок

Заключение

Стек и куча – это два способа распределения памяти в Java. Из этой статьи мы узнали, как они работают и когда их лучше использовать для разработки программ на Java.

Чтобы узнать больше об управлении памятью в Java, ознакомьтесь с этой статьей. Мы также затронули сборщик мусора JVM, который кратко обсуждается в этой статье.

Оригинал