Для оптимального запуска приложения 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);
}
}
Давайте проанализируем шаг за шагом:
- Когда входим в метод main(), в стеке создается место для хранения примитивов и ссылок этого метода.
- Стек напрямую хранит примитивное значение integer id.
- В стеке также будет создана ссылочная переменная person типа Person, которая будет указывать на реальный объект в куче.
- Вызов параметризованного конструктора Person(int, String) из main() выделит дополнительную память поверх предыдущего стека. Она будет хранить:
- Ссылку на объект this вызывающего объекта в стеке
- id примитивного значения в стеке
- Ссылочную переменную String аргумента name, которая будет указывать на фактическую строку из пула строк в куче.
- Метод main() – это дальнейший вызов статического метода buildPerson(), для которого дальнейшее выделение будет происходить в стеке поверх предыдущего. Снова будут сохранены переменные в порядке, описанном выше.
- Однако в куче будут храниться все переменные экземпляра для вновь созданного объекта person типа Person.
Посмотрим на это распределение на диаграмме ниже:

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