Стек и куча в Java

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

1. Стек

Стек работает по схеме LIFO (последним вошел, первым вышел). Всякий раз, когда вызывается новый метод, содержащий примитивные значения или ссылки на объекты, то на вершине стека под них выделяется блок памяти. Из этого можно сделать вывод, что стек хранит значения примитивных переменных, создаваемых в методах, а также ссылки на объекты в куче на которые ссылается метод.
Когда метод завершает выполнение, блок памяти (frame), отведенный для его нужд, очищается, и пространство становится доступным для следующего метода. При этом поток выполнения программы возвращается к месту вызова этого метода с последующим переходом к следующей строке кода.

Основные особенности стека

Помимо того, что мы рассмотрели, существуют и другие особенности стека:

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

2. Куча

Эта область памяти используется для динамического выделения памяти для объектов и классов JRE во время выполнения. Новые объекты всегда создаются в куче, а ссылки на них хранятся в стеке.
Эти объекты имеют глобальный доступ и могут быть получены из любого места программы.
Эта область памяти разбита на несколько более мелких частей, называемых поколениями:
  1. Young Generation — область где размещаются недавно созданные объекты. Когда она заполняется, происходит быстрая сборка мусора
  2. Old (Tenured) Generation — здесь хранятся долгоживущие объекты. Когда объекты из Young Generation достигают определенного порога «возраста», они перемещаются в Old Generation
  3. Permanent Generation — эта область содержит метаинформацию о классах и методах приложения, но начиная с Java 8 данная область памяти была упразднена. Подробнее об этом можно узнать из нашей прошлой статьи, а также посмотрев видео
Мы можем управлять размерами кучи в зависимости от наших требований.

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

Помимо рассмотренных ранее, куча имеет следующие ключевые особенности:

  • Когда эта область памяти полностью заполняется, Java бросает java.lang.OutOfMemoryError
  • Доступ к ней медленнее, чем к стеку
  • Эта память, в отличие от стека, автоматически не освобождается. Для сбора неиспользуемых объектов используется сборщик мусора
  • В отличие от стека, куча не является потокобезопасной и ее необходимо контролировать, правильно синхронизируя код

3. Примеры

Основываясь на рассмотренной ранее информации, рассмотрим пример кода и разберемся, как происходит управление памятью:
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(), в стеке будет выделено пространство для хранения примитивов и ссылок этого метода:
  • примитивное значение id типа int будет храниться непосредственно в стеке;
  • ссылочная переменная name типа String будет создана в стеке, но сама строка "John" будет храниться в области, называемой String Pool (является частью Кучи);
  • ссылочная переменная person типа Person будет также создана в памяти стека, но будет указывать на объект, расположенный в куче;
2. Для вызова конструктора с параметрами Person (int, String) из метода main() в стеке, поверх предыдущего вызова метода main(), будет выделен блок памяти, который будет хранить:
  • this — ссылка на текущий объект;
  • примитивное значение id ;
  • ссылочную переменную name типа String, которая указывает на объект строки из пула строк;
3. В методе main дополнительно вызывается метод buildPerson для которого будет выделен блок памяти в стеке поверх предыдущего вызова. Этот блок снова сохранит переменные способом, описанным выше.
4. Для вновь созданного объекта person типа Person все переменные будут сохранены в памяти кучи.
Резюме
Прежде чем завершить эту статью, давайте суммируем различия между памятью стека и пространством кучи:
Свойства
Стек
Куча
Использование приложением
Для каждого потока используется свой стек
Пространство кучи является общим для всего приложения
Размер
Предел размера стека определен операционной системой
Размер кучи не ограничен
Хранение
Хранит примитивы и ссылки на объекты
Все созданные объекты хранятся в куче
Порядок
Работает по схеме последним вошел, первым вышел (LIFO)
Доступ к этой памяти осуществляется с помощью сложных методов управления памятью, включая Young Generation, Old и Permanent Generation
Существование
Память стека существует пока выполняется текущий метод
Пространство кучи существует пока работает приложение
Скорость
Обращение к памяти стека происходит значительно быстрее, чем к памяти кучи
Медленнее, чем стек
Выделение и освобождение памяти
Эта память автоматически выделяется и освобождается, когда метод вызывается и завершается соответственно
Память в куче выделяется, когда создается новый объект и освобождается сборщиком мусора, когда в приложении не остается ни одной ссылки на его
Оригинал статьи «Stack Memory and Heap Space in Java»
Оцените статью, если она вам понравилась!