Структура памяти в Java: стек и куча - Вопросы
Всего: 5 вопросов
1. Чем стек (stack) отличается от кучи (heap) в Java?
Чем стек (stack) отличается от кучи (heap) в Java?
В куче размещаются все объекты и массивы, созданные через new, вместе с их полями. В стеке хранятся кадры вызовов методов: локальные переменные примитивных типов, ссылки на объекты из кучи и параметры метода.
Стек работает по принципу LIFO: при вызове метода кадр кладётся на вершину стека, при выходе — снимается, и память освобождается мгновенно, без участия сборщика мусора. В куче память освобождает сборщик мусора, когда объект становится недостижимым по ссылкам.
Стек свой у каждого потока и невелик (задаётся ключом -Xss), куча одна на всю JVM и значительно больше (-Xms / -Xmx). Доступ к стеку быстрее: не нужно разыменовывать ссылку и не работает GC. При переполнении стека возникает StackOverflowError, при переполнении кучи — OutOfMemoryError: Java heap space.
2. Где хранятся поля объекта: в стеке или в куче? Всегда ли объект создаётся в куче?
Где хранятся поля объекта: в стеке или в куче? Всегда ли объект создаётся в куче?
Все поля объекта — и ссылочные, и примитивные — хранятся внутри самого объекта, то есть в куче. Правило «примитивы лежат в стеке» верно только для локальных переменных метода: int x, объявленный как поле класса, живёт в куче вместе с объектом.
Объект всегда создаётся в куче, а в стеке находится только ссылка на него — значение ссылочной переменной это адрес объекта, а не сам объект.
Единственное уточнение: JIT-компилятор умеет выполнять escape analysis. Если объект не покидает метод, компилятор может выполнить скалярную замену и разместить его поля в стеке или регистрах. Это оптимизация времени выполнения, на модель памяти и на то, как вы пишете код, она не влияет.
3. Java передаёт объекты в методы по ссылке или по значению?
Java передаёт объекты в методы по ссылке или по значению?
Java всегда передаёт аргументы по значению. Для примитива копируется само значение, для ссылочного типа — значение ссылки, то есть адрес объекта в куче.
Отсюда два следствия. Метод может изменить поля переданного объекта: копия ссылки указывает на тот же самый объект в куче, поэтому изменения видны в вызывающем коде. Но метод не может заставить переменную вызывающего кода указывать на другой объект: присваивание point = new Point() внутри метода меняет только локальную копию ссылки в его кадре стека.
4. Почему локальные переменные потокобезопасны, а объекты в куче — нет?
Почему локальные переменные потокобезопасны, а объекты в куче — нет?
У каждого потока свой собственный стек. Локальные переменные метода лежат в кадре стека этого потока и по определению недоступны другим потокам, поэтому они потокобезопасны без всякой синхронизации.
Куча одна на всю JVM и общая для всех потоков. Объект, созданный в одном потоке, может быть прочитан и изменён из другого, если туда передали ссылку на него — поэтому общие объекты приходится защищать с помощью synchronized, volatile и других средств синхронизации.
При этом объект в куче не «виден отовсюду» сам по себе: добраться до него можно только по цепочке ссылок. Если ссылку никуда не передали и она пропала, объект становится недостижимым и будет собран сборщиком мусора.
5. Когда возникают StackOverflowError и OutOfMemoryError: Java heap space?
Когда возникают StackOverflowError и OutOfMemoryError: Java heap space?
StackOverflowError возникает, когда в стеке потока закончилось место. Самая частая причина — рекурсия без условия выхода: каждый вызов кладёт на стек новый кадр, пока стек не переполнится. Размер стека потока задаётся ключом -Xss.
OutOfMemoryError: Java heap space возникает, когда в куче не осталось места для нового объекта, а сборщик мусора ничего не может освободить. Типичный сценарий — утечка памяти: коллекция бесконечно растёт и удерживает ссылки на объекты, поэтому они остаются достижимыми. Размер кучи задаётся ключами -Xms (начальный) и -Xmx (максимальный), а предел Metaspace — ключом -XX:MaxMetaspaceSize.