Stack vs Heap в Java: Практическое руководство по распределению памяти
Что такое память стека (Stack)?
В Java стек — это непрерывная область памяти, которая хранит кадры вызовов методов, локальные переменные и ссылочные переменные. Каждый раз при вызове метода на стек помещается новый кадр. Память стека организована по принципу Last-In, First-Out (LIFO), что означает: самый последний вызов удаляется первым при возврате из метода.
Что такое память кучи (Heap)?
Куча — это область, где создаются объекты Java. Она содержит все экземпляры классов и, опционально, ссылочные переменные, указывающие на эти объекты. В отличие от стека, куча не привязана к времени жизни одного вызова метода, позволяя объектам существовать за пределами области видимости, в которой они были созданы.
Распределение памяти в Java
Распределение памяти в Java — это процесс резервирования виртуальной памяти для данных программы. Переменные не выделяются при объявлении; вместо этого создается ссылка. Когда используется оператор new, JVM выделяет место в куче для объекта.
Java делит память на четыре отдельные секции для эффективного управления:
- Heap (Куча)
- Stack (Стек)
- Code (Байт-код)
- Static (Данные класса)
- Секция Code содержит скомпилированный байт-код.
- Стек хранит кадры методов, локальные переменные и ссылочные переменные.
- Куча хранит объекты и иногда ссылочные переменные.
- Static секция хранит статические поля и методы.
Локальные переменные vs Переменные экземпляра
Переменные экземпляра объявляются внутри класса, но вне любого метода:
class Student {
int num; // переменная экземпляра
public void showData() {}
}
Локальные переменные объявляются внутри метода (или как параметры метода):
public void sum(int a) {
int x = a + 3; // a и x — локальные переменные
}
Stack vs Heap в действии
Рассмотрим пример, иллюстрирующий взаимодействие стека и кучи при вызовах методов.
Предположим, метод main вызывает m1():
public void m1() {
int x = 20;
}
Кадр для m1 помещается на стек. Локальная переменная x сохраняется в этом кадре.
Когда m1 вызывает m2(int b), новый кадр добавляется поверх кадра m1:
public void m2(int b) {
boolean c;
}
Аналогично, m2 вызывает m3(), создавая еще один кадр:
Внутри m3 создается объект Account в куче, а его ссылка ref находится в стеке:
class Account {
int p;
int q;
}
public void m3() {
Account ref = new Account(); // дополнительный код
}
Ссылочная переменная ref создается в кадре стека m3 и указывает на объект Account, выделенный в куче.
Когда m3 завершается, его кадр стека снимается, оставляя объект Account без ссылок. Он становится пригодным для сборки мусора.
Тот же процесс повторяется для m2 и m1, в конечном итоге возвращая управление в main.
Ссылочные переменные как переменные экземпляра
Рассмотрим класс A, который хранит ссылку на экземпляр B:
public static void main(String[] args) {
A parent = new A(); // дополнительный код
}
class A {
B child = new B();
int e;
}
class B {
int c;
int d;
}
Здесь ссылка child хранится в куче как часть объекта A, указывая на свой собственный экземпляр B.
Основные выводы
- Каждый вызов метода создает новый кадр в стеке.
- Локальные переменные находятся в стеке; переменные экземпляра — в куче.
- Ссылочные переменные хранятся в стеке, но указывают на объекты в куче.
- Когда кадр стека снимается, его локальные и ссылочные переменные отбрасываются, а любые оставшиеся без ссылок объекты кучи становятся пригодными для сборки мусора.
java
- Переменные и литералы в Java: подробное руководство
- Классы и объекты в Java: практическое руководство
- Абстрактные классы и методы в Java: полное руководство
- Вложенные и внутренние классы в Java: типы, примеры и лучшие практики
- Java throw vs throws: мастерство обработки исключений с практическими примерами
- Типы аннотаций в Java: предопределенные, кастомные и мета-аннотации
- Освоение Java Autoboxing и Unboxing: практические примеры и лучшие практики
- Замена строк в Java: replace(), replaceAll() и replaceFirst() подробно
- Переопределение методов в Java: настройка поведения суперкласса
- Java 10: размещение кучи на NV-DIMM через -XX:AllocateHeapAt