Структура памяти в Java: стек и куча - Вопросы

Всего: 5 вопросов

1. 

Чем стек (stack) отличается от кучи (heap) в Java?

В куче размещаются все объекты и массивы, созданные через new, вместе с их полями. В стеке хранятся кадры вызовов методов: локальные переменные примитивных типов, ссылки на объекты из кучи и параметры метода.

Стек работает по принципу LIFO: при вызове метода кадр кладётся на вершину стека, при выходе — снимается, и память освобождается мгновенно, без участия сборщика мусора. В куче память освобождает сборщик мусора, когда объект становится недостижимым по ссылкам.

Стек свой у каждого потока и невелик (задаётся ключом -Xss), куча одна на всю JVM и значительно больше (-Xms / -Xmx). Доступ к стеку быстрее: не нужно разыменовывать ссылку и не работает GC. При переполнении стека возникает StackOverflowError, при переполнении кучи — OutOfMemoryError: Java heap space.

2. 

Где хранятся поля объекта: в стеке или в куче? Всегда ли объект создаётся в куче?

Все поля объекта — и ссылочные, и примитивные — хранятся внутри самого объекта, то есть в куче. Правило «примитивы лежат в стеке» верно только для локальных переменных метода: int x, объявленный как поле класса, живёт в куче вместе с объектом.

Объект всегда создаётся в куче, а в стеке находится только ссылка на него — значение ссылочной переменной это адрес объекта, а не сам объект.

Единственное уточнение: JIT-компилятор умеет выполнять escape analysis. Если объект не покидает метод, компилятор может выполнить скалярную замену и разместить его поля в стеке или регистрах. Это оптимизация времени выполнения, на модель памяти и на то, как вы пишете код, она не влияет.

3. 

Java передаёт объекты в методы по ссылке или по значению?

Java всегда передаёт аргументы по значению. Для примитива копируется само значение, для ссылочного типа — значение ссылки, то есть адрес объекта в куче.

Отсюда два следствия. Метод может изменить поля переданного объекта: копия ссылки указывает на тот же самый объект в куче, поэтому изменения видны в вызывающем коде. Но метод не может заставить переменную вызывающего кода указывать на другой объект: присваивание point = new Point() внутри метода меняет только локальную копию ссылки в его кадре стека.

4. 

Почему локальные переменные потокобезопасны, а объекты в куче — нет?

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

Куча одна на всю JVM и общая для всех потоков. Объект, созданный в одном потоке, может быть прочитан и изменён из другого, если туда передали ссылку на него — поэтому общие объекты приходится защищать с помощью synchronized, volatile и других средств синхронизации.

При этом объект в куче не «виден отовсюду» сам по себе: добраться до него можно только по цепочке ссылок. Если ссылку никуда не передали и она пропала, объект становится недостижимым и будет собран сборщиком мусора.

5. 

Когда возникают StackOverflowError и OutOfMemoryError: Java heap space?

StackOverflowError возникает, когда в стеке потока закончилось место. Самая частая причина — рекурсия без условия выхода: каждый вызов кладёт на стек новый кадр, пока стек не переполнится. Размер стека потока задаётся ключом -Xss.

OutOfMemoryError: Java heap space возникает, когда в куче не осталось места для нового объекта, а сборщик мусора ничего не может освободить. Типичный сценарий — утечка памяти: коллекция бесконечно растёт и удерживает ссылки на объекты, поэтому они остаются достижимыми. Размер кучи задаётся ключами -Xms (начальный) и -Xmx (максимальный), а предел Metaspace — ключом -XX:MaxMetaspaceSize.

Страница 1 из 1