1. введение
Чтобы запустить приложение оптимальным образом, JVM делит память на память стека и кучи. Всякий раз, когда мы объявляем новые переменные и объекты, вызываем новый метод, объявляем Строку или выполняем аналогичные операции, JVM выделяет память для этих операций либо из памяти стека, либо из пространства кучи.
В этом уроке мы обсудим эти модели памяти. Мы рассмотрим некоторые ключевые различия между ними, как они хранятся в оперативной памяти, какие функции они предлагают и где их использовать.
2. Стековая память в Java
Стековая память в Java используется для статического выделения памяти и выполнения потока. Он содержит примитивные значения, специфичные для метода, и ссылки на объекты, находящиеся в куче, на которые ссылаются из метода.
Доступ к этой памяти осуществляется в порядке “Последний вход-первый выход” (LIFO). Всякий раз, когда вызывается новый метод, создается новый блок в верхней части стека, содержащий значения, характерные для этого метода, такие как примитивные переменные и ссылки на объекты.
Когда метод завершает выполнение, соответствующий кадр стека сбрасывается, поток возвращается к вызывающему методу, и пространство становится доступным для следующего метода.
2.1. Основные характеристики стековой памяти
Помимо того, что мы обсуждали до сих пор, ниже приведены некоторые другие особенности стековой памяти:
- Он растет и сжимается по мере вызова и возврата новых методов соответственно
- Переменные внутри стека существуют только до тех пор, пока работает метод, который их создал
- Он автоматически выделяется и освобождается, когда метод завершает выполнение
- Если эта память заполнена, Java выдает java.lang.StackOverflowError
- Доступ к этой памяти осуществляется быстро по сравнению с памятью кучи
- Эта память является потокобезопасной, так как каждый поток работает в своем собственном стеке
3. Пространство кучи в Java
Пространство кучи в Java используется для динамического выделения памяти для объектов Java и классов JRE во время выполнения . Новые объекты всегда создаются в пространстве кучи, а ссылки на эти объекты хранятся в памяти стека.
Эти объекты имеют глобальный доступ и могут быть доступны из любой точки приложения.
Эта модель памяти далее разбивается на более мелкие части, называемые поколениями, это:
- Молодое поколение – именно здесь выделяются и стареют все новые объекты. Незначительная сборка мусора происходит, когда это заполняется
- Старое или арендованное поколение – здесь хранятся давно сохранившиеся объекты. Когда объекты хранятся в Молодом поколении, устанавливается пороговое значение для возраста объекта, и когда это пороговое значение достигнуто, объект перемещается в старое поколение
- Постоянная генерация – это состоит из метаданных JVM для классов среды выполнения и методов приложения
Эти различные части также обсуждаются в этой статье – Разница между JVM, JRE и JDK.
Мы всегда можем манипулировать размером памяти кучи в соответствии с нашими требованиями. Для получения дополнительной информации посетите эту связанную статью Baeldung .
3.1. Основные характеристики кучной памяти Java
Помимо того, что мы обсуждали до сих пор, ниже приведены некоторые другие особенности пространства кучи:
- Доступ к нему осуществляется с помощью сложных методов управления памятью, которые включают в себя Молодое поколение, Старое или Штатное поколение и Постоянное поколение
- Если пространство кучи заполнено, Java выдает java.lang.OutOfMemoryError
- Доступ к этой памяти относительно медленнее, чем к стековой памяти
- Эта память, в отличие от стека, не освобождается автоматически. Ему нужен сборщик мусора, чтобы освободить неиспользуемые объекты, чтобы сохранить эффективность использования памяти
- В отличие от стека, куча не является потокобезопасной и должна быть защищена путем правильной синхронизации кода
4. Пример
Основываясь на том, что мы узнали до сих пор, давайте проанализируем простой 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 аргумент имя , которая будет указывать на фактическую строку из пула строк в памяти кучи
- Метод main далее вызывает статический метод build Person () , для которого дальнейшее выделение будет происходить в памяти стека поверх предыдущего. Это снова сохранит переменные способом, описанным выше.
- Однако для вновь созданного объекта person типа Person все переменные экземпляра будут храниться в памяти кучи.
Это распределение объясняется на этой диаграмме: